我有点知道F#中异步编程的语法。例如。

let downloadUrl(url:string) = async {
  let req = HttpWebRequest.Create(url)
  // Run operation asynchronously
  let! resp = req.AsyncGetResponse()
  let stream = resp.GetResponseStream()
  // Dispose 'StreamReader' when completed
  use reader = new StreamReader(stream)
  // Run asynchronously and then return the result
  return! reader.AsyncReadToEnd() }

在F#专家书籍(和许多其他资料)中,他们说



我也知道执行异步操作时会创建一个新线程。我最初的理解是,异步操作后有两个并行线程,一个线程执行I / O,一个线程继续同时执行异步主体。

但是在这个例子中,我感到困惑
  let! resp = req.AsyncGetResponse()
  let stream = resp.GetResponseStream()

如果resp尚未启动并且异步主体中的线程想要GetResponseStream会发生什么?这可能是错误吗?

所以也许我最初的理解是错误的。 F#专家书中带引号的句子实际上意味着“创建新线程,挂起当前线程,当新线程完成后,唤醒主体线程并继续”,但在这种情况下,我看不到我们可以保存任何时候。

在最初的理解中,当在一个异步块中有多个独立 IO操作时,可以节省时间,从而可以在不相互干预的情况下同时完成这些操作。但是在这里,如果没有得到响应,就无法创建流。只有我有信息流,我才能开始阅读信息流。时间在哪里?

最佳答案

在此示例中,“异步”与并发或节省时间无关,而是与提供一个良好的编程模型而不阻塞(读:浪费)线程有关。

如果使用其他编程语言,通常有两种选择:

您可以阻止,通常可以通过调用同步方法来实现。缺点是线程在等待磁盘或网络I / O或所拥有的资源时被消耗,并且没有做任何有用的工作。好处是它的代码简单(普通代码)。

您可以使用回调来异步调用并在操作完成时获取通知。优点是您不阻塞线程(这些线程可以返回到ThreadPool中,并且在操作完成后将使用新的ThreadPool线程来回调您)。缺点是将一个简单的代码块划分为一堆回调方法或lambda,并且在整个回调中维护状态/控制流/异常处理很快变得非常复杂。

因此,您处于困境与困境之间;您要么放弃简单的编程模型,要么浪费线程。

F#模型提供了两个方面的优势:您不阻塞线程,但保留了直接的编程模型。像let!这样的构造使您可以在异步块中间进行“线程跳跃”,因此在类似

Blah1()
let! x = AsyncOp()
Blah2()
Blah1可以在ThreadPool线程#13上运行,但是AsyncOp会将那个线程释放回ThreadPool。稍后,当AsyncOp完成时,其余代码将在可用线程(例如ThreadPool线程#20)上备份,该线程将x绑定(bind)到结果,然后运行Blah2。在琐碎的客户端应用程序中,这几乎无关紧要(除非确保不阻止UI线程),但是在执行I / O的服务器应用程序中(线程通常是宝贵的资源-线程很昂贵,您不能浪费它们)阻塞)非阻塞I / O通常是扩大应用程序规模的唯一方法。 F#使您可以编写非阻塞I / O,而无需使程序降级为大量的意大利面条式代码回调。

也可以看看

Best practices to parallelize using async workflow

How to do chained callbacks in F#?

http://cs.hubfs.net/forums/thread/8262.aspx

关于f# - 了解F#异步编程,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/2444676/

10-17 00:47