代码之家  ›  专栏  ›  技术社区  ›  Yin Zhu

理解异步编程

  •  21
  • Yin Zhu  · 技术社区  · 15 年前

    我知道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() }
    

    让我来!var=expr只是表示“执行异步操作” 继续执行计算体的其余部分“

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

    但在这个例子中,我对

      let! resp = req.AsyncGetResponse()
      let stream = resp.GetResponseStream()
    

    resp GetResponseStream ? 这是一个可能的错误吗?

    所以也许我最初的理解是错误的。F#专家书中引用的句子实际上意味着“创建一个新线程,挂起当前线程,当新线程完成时,唤醒主体线程并继续”,但在这种情况下,我认为我们不能节省任何时间。

    在最初的理解中,当有几个 独立的 一个异步块中的IO操作,以便它们可以同时执行,而无需相互干预。但是在这里,如果我没有得到响应,我就不能创建流;只有我有溪流,我才能开始阅读溪流。时间到哪里去了?

    4 回复  |  直到 15 年前
        1
  •  23
  •   Community CDub    8 年前

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

    ,通常通过调用同步方法。缺点是线程在等待磁盘或网络I/O或您所拥有的东西时被消耗,并且没有做任何有用的工作。优点是代码简单(普通代码)。

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

    F#模型提供了两个世界中最好的;您不阻塞线程,但是您保持了简单的编程模型。构造像 let!

    Blah1()
    let! x = AsyncOp()
    Blah2()
    

    Blah1 可以在线程池线程#13上运行,但AsyncOp会将该线程释放回线程池。稍后,当AsyncOp完成时,剩下的代码将在一个可用的线程(比如说,ThreadPool thread#20)上启动,该线程绑定 x 然后运行 Blah2

    另请参见

    Best practices to parallelize using async workflow

    How to do chained callbacks in F#?

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

        2
  •  9
  •   Tomas Petricek    15 年前

    我认为最重要的是要了解 异步工作流 是的,他们是 相继的 与用F#(或C#)编写的普通代码的顺序相同。你有一些 let 异步工作流

    异步工作流 是的,他们是 . 这意味着您可以在某些应用程序中执行操作 let! 在F#计算中,表达式总是表示 非标准 Maybe monad ,也可以是异步工作流的非阻塞执行)。

    Timer 不阻塞任何线程(例如我的书第13章, source is available here ):

    // Primitive that delays the workflow
    let Sleep(time) = 
      // 'FromContinuations' is the basic primitive for creating workflows
      Async.FromContinuations(fun (cont, econt, ccont) ->
        // This code is called when workflow (this operation) is executed
        let tmr = new System.Timers.Timer(time, AutoReset=false)
        tmr.Elapsed.Add(fun _ -> 
          // Run the rest of the computation
          cont())
        tmr.Start() )
    

    对于并行或并发编程,使用F#异步工作流的方法也有很多种,但是这些方法只是对F#工作流或构建在其上的库的更复杂的使用—它们利用了前面描述的非阻塞行为。

    • StartChild 要在后台启动工作流,该方法将为您提供一个可以使用的正在运行的工作流(使用 让我来! )稍后在工作流中等待完成,同时可以继续做其他事情。这与 任务 但它是异步运行的,因此更适合于I/O操作。

    • Async.Parallel 创建多个工作流并等待所有工作流完成(这对于数据并行操作非常有用)。这与PLINQ相似,但同样, async 如果你做一些I/O操作会更好。

    • MailboxProcessor 它允许您使用消息传递样式(Erlang样式)编写并发应用程序。对于许多问题,这是线程的一个很好的替代方案。

        3
  •  3
  •   itowlson    15 年前

    这并不是“获得的时间”的问题,异步编程不会使数据到达更快。相反,它是关于简化并发的心智模型。

    例如,在C#中,如果要执行异步操作,就需要开始处理回调,并将本地状态传递给这些回调,以此类推。对于一个简单的操作,比如 专家F# 对于两个异步操作,您将看到三个看似独立的方法(启动器和两个回调)。这掩盖了工作流的顺序性、概念上的线性性质:do请求、读取流、打印结果。

    相比之下,F#async工作流代码使程序的顺序非常清晰。只需看一段代码,就可以准确地判断出以什么顺序发生的事情。你不需要追踪回叫。

    也就是说,如果有几个独立的异步操作正在进行,F#确实有一些机制可以帮助节省时间。例如,您可以同时启动多个异步工作流,它们将并行运行。但是在一个异步工作流实例中,它主要是关于简单性、安全性和可理解性的:让您可以像推理C#风格的同步语句序列一样轻松地推理异步语句序列。

        4
  •  2
  •   kvb    15 年前

    这是个好问题。需要注意的是,在一个 async 积木是 异步 块基本上让处理器有时间 其他 异步