简体   繁体   English

理解F#异步编程

[英]Understanding F# Asynchronous Programming

I kind of know the syntax of asynchronous programming in F#. 我有点了解F#中异步编程的语法。 Eg 例如

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() }

In F# expert book (and many other sources), they say like 在F#专家书(和许多其他来源),他们说喜欢

let! 让! var = expr simply means "perform the asynchronous operation expr and bind the result to var when the operation completes. Then continue by executing the rest of the computation body" var = expr只是表示“执行异步操作expr并在操作完成时将结果绑定到var。然后继续执行其余的计算主体”

I also know that a new thread is created when performing async operation. 我也知道在执行异步操作时会创建一个新线程。 My original understanding was that there are two parallel threads after the async operation, one doing I/O and one continuing to execute the async body at the same time. 我最初的理解是异步操作后有两个并行线程,一个执行I / O,另一个继续同时执行异步主体。

But in this example, I am confused at 但在这个例子中,我很困惑

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

What happens if resp has not started yet and the thread in the async body wants to GetResponseStream ? 如果resp尚未启动并且异步体中的线程想要GetResponseStream什么? Is this a possible error? 这可能是错误吗?

So maybe my original understanding was wrong. 也许我原来的理解是错误的。 The quoted sentences in the F# expert book actually means that "creating a new thread, hang the current thread up, when the new thread finishes, wake up the body thread and continue", but in this case I don't see we could save any time. F#专家书中引用的句子实际上意味着“创建一个新线程,挂起当前线程,当新线程完成时,唤醒正文线程并继续”,但在这种情况下我看不到我们可以保存任何时候。

In the original understanding, the time is saved when there are several independent IO operations in one async block so that they could be done at the same time without intervention with each other. 在最初的理解中,当一个异步块中有多个独立的 IO操作时,可以节省时间,这样它们可以在不相互干预的情况下同时完成。 But here, if I don't get the response, I cannot create the stream; 但是在这里,如果我没有得到响应,我就无法创建流; only I have stream, I can start reading the stream. 只有我有流,我可以开始阅读流。 Where's the time gained? 获得的时间在哪里?

The "async" in this example is not about concurrency or saving time, rather it's about providing a good programming model without blocking (read: wasting) threads. 此示例中的“异步”不是关于并发或节省时间,而是关于提供良好的编程模型而不阻塞(读取:浪费)线程。

If using other programming languages, typically you have two choices: 如果使用其他编程语言,通常有两种选择:

You can block , typically by calling synchronous methods. 您可以阻止 ,通常通过调用同步方法。 The disadvantage is that the thread is consumed and doing no useful work while it waits for the disk or network I/O or what have you. 缺点是线程在等待磁盘或网络I / O或您拥有的东西时被占用并且没有做任何有用的工作。 The advantage is it the code simple (normal code). 优点是代码简单(普通代码)。

You can use callbacks to call asynchronously and get notifications when operations complete. 您可以使用回调来异步调用,并在操作完成时收到通知。 The advantage is you don't block threads (these threads can be returned eg to the ThreadPool and a new ThreadPool thread will be used when the operation completes to call you back). 优点是您不会阻塞线程(这些线程可以返回到例如ThreadPool,并且当操作完成后将使用新的ThreadPool线程来回调)。 The disadvantage is that a simple block of code gets divided up into a bunch of callback methods or lambdas, and it quickly becomes very complicated to maintain state/control-flow/exception-handling across the callbacks. 缺点是一个简单的代码块被分成一堆回调方法或lambda,并且很快就会在回调中维护状态/控制流/异常处理变得非常复杂。

So you're between a rock and a hard place; 所以你在岩石和坚硬的地方之间; you either give up the simple programming model or you waste threads. 你要么放弃简单的编程模型,要么浪费线程。

The F# model gives the best of both worlds; F#模型提供了两全其美的效果; you don't block threads, but you keep the straightforward programming model. 你不会阻止线程,但你保持简单的编程模型。 Constructs like let! let! enable you to 'thread-hop' in the middle of an async block, so in code like 使您能够在异步块的中间“跳线”,所以在代码中

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

Blah1 may run on, say, ThreadPool thread #13, but then AsyncOp will release that thread back to the ThreadPool. Blah1可以在ThreadPool线程#13上运行,但随后AsyncOp会将该线程释放回ThreadPool。 Later when the AsyncOp completes, the rest of the code will start back up on an available thread (maybe, say, ThreadPool thread #20) which binds x to the result and then runs Blah2 . 稍后当AsyncOp完成时,其余代码将在可用线程(可能是ThreadPool线程#20)上开始备份,该线程将x绑定到结果,然后运行Blah2 In trivial client apps this rarely matters (except when ensuring you don't block the UI thread), but in server apps that do I/O (where threads are often a precious resource - threads are expensive and you can't waste them by blocking) non-blocking I/O is often the only way to make an application scale. 在简单的客户端应用程序中,这很少重要(除非确保您不阻止UI线程),但在执行I / O的服务器应用程序中(线程通常是宝贵的资源 - 线程很昂贵,您不能浪费它们阻塞)非阻塞I / O通常是使应用程序扩展的唯一方法。 F# enables you to write non-blocking I/O without having the program degrade into a mass of spaghetti-code callbacks. F#使您能够编写非阻塞I / O,而无需将程序降级为大量的意大利面条代码回调。

See also 也可以看看

Best practices to parallelize using async workflow 使用异步工作流并行化的最佳实践

How to do chained callbacks in F#? 如何在F#中进行链式回调?

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

I think the most important thing to understand about asynchronous workflows is that they are sequential in the same way as ordinary code written in F# (or C#, for that matter) is sequential. 我认为理解异步工作流最重要的是它们的顺序与用F#编写的普通代码(或C#,就此而言)顺序相同。 You have some let bindings that evaluate in the usual order and some expressions (that may have side-effects). 你有一些let绑定,以通常的顺序和一些表达式(可能有副作用)进行评估。 In fact, asynchronous workflows often look more like imperative code. 实际上, 异步工作流通常看起来更像命令式代码。

The second important aspect of asynchronous workflows is that they are non-blocking . 异步工作流的第二个重要方面是它们是非阻塞的 This means that you can have operations that are executed in some non-standard way and do not block the thread while executing. 这意味着您可以执行以某种非标准方式执行的操作,并且在执行时不会阻塞该线程。 (In general, let! in F# computation expressions always signals that there is some non-standard behavior - it may be possibility to fail without producing result in the Maybe monad , or it may be non-blocking execution for asynchronous workflows). (一般来说, let!在F#计算表达式中始终表示存在一些非标准行为 - 如果没有在Maybe monad中产生结果,则可能会失败,或者它可能是异步工作流的非阻塞执行)。

Technically speaking, non-blocking execution is implemented by registering some callback that will be triggered when the operation completes. 从技术上讲,非阻塞执行是通过注册一些将在操作完成时触发的回调来实现的。 Relatively simple example is an asynchronous workflow that waits some specified time - this can be implemented using Timer without blocking any threads (Example from chapter 13 of my book, source is available here ): 相对简单的示例是等待某个指定时间的异步工作流 - 这可以使用Timer实现而不会阻塞任何线程(示例来自我的书的第13章, 源代码可在此处获得 ):

// 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() )

There are also several ways to use F# asynchronous workflows for parallel or concurrent programming, however these are just more sophisticated uses of F# workflows or libraries built on top of them - they take the advantage of non-blocking behavior described earlier. 还有几种方法可以将F#异步工作流用于并行或并发编程,但这些只是F#工作流或基于它们构建的库的更复杂的用法 - 它们利用了前面描述的非阻塞行为。

  • You can use StartChild to start a workflow in background - the method gives you a running workflow that you can use (using let! ) later in the workflow to wait for completion, while you can continue doing other things. 您可以使用StartChild在后台启动工作流 - 该方法为您提供了一个正在运行的工作流,您可以在工作流中稍后使用(使用let! )等待完成,同时您可以继续执行其他操作。 This is similar to Tasks in .NET 4.0, but it runs asynchronously, so it is more suitable for I/O operations. 这类似于.NET 4.0中的任务 ,但它以异步方式运行,因此更适合I / O操作。

  • You can use Async.Parallel to create multiple workflows and wait until all of them complete (which is great for data-parallel operations). 您可以使用Async.Parallel创建多个工作流并等待所有工作流完成(这对于数据并行操作非常Async.Parallel )。 This is similar to PLINQ, but again, async is better if you do some I/O operations. 这类似于PLINQ,但同样,如果进行一些I / O操作, async会更好。

  • Finally, you can use MailboxProcessor which allows you to write concurrent applications using the message-passing style (Erlang style). 最后,您可以使用MailboxProcessor ,它允许您使用消息传递样式(Erlang样式)编写并发应用程序。 This is a great alternative to threads for many problems. 对于许多问题,这是线程的一个很好的替代方案。

It's not really about "time gained." 这不是关于“获得的时间”。 Asynchronous programming won't make the data arrive any faster. 异步编程不会使数据更快地到达。 Rather, it's about simplifying the mental model for concurrency. 相反,它是关于简化并发的心智模型。

In C#, for example, if you want to perform an async operation, you need to start mucking around with callbacks, and passing local state to those callbacks, and so on. 例如,在C#中,如果要执行异步操作,则需要开始使用回调,并将本地状态传递给这些回调,依此类推。 For a simple operation like the one in Expert F# with two async operations, you're looking at three seemingly separate methods (the initiator and two callbacks). 对于像Expert F#中的两个异步操作这样的简单操作,您将看到三个看似独立的方法(启动器和两个回调)。 This disguises the sequential, conceptually linear nature of the workflow: do request, read stream, print results. 这掩盖了工作流的顺序概念线性特性:请求,读取流,打印结果。

By contrast, the F# async workflow code makes the sequencing of the program very clear. 相比之下,F#异步工作流程代码使程序的排序非常清晰。 You can tell exactly what is happening in what order just by looking at the one block of code. 只需查看一个代码块,就可以确切地知道发生了什么。 You don't need to chase callbacks. 你不需要追逐回调。

That said, F# does have mechanisms which can help save time if there are several independent async operations in progress. 也就是说,如果正在进行多个独立的异步操作,F#确实有一些机制可以帮助节省时间。 For example, you can kick off multiple async workflows at the same time, and they will run in parallel. 例如,您可以同时启动多个异步工作流,它们将并行运行。 But within a single async workflow instance, it's primarily about simplicity, safety and understandability: about letting you reason about asynchronous sequences of statements as easily as you reason about C#-style synchronous sequences of statements. 但是在单个异步工作流实例中,它主要是关于简单性,安全性和可理解性:关于让你理解异步语句序列就像你推理C#式同步语句序列一样容易。

This is a good question. 这是一个很好的问题。 It is important to note that multiple statements in an async blocks are not run in parallel. 请务必注意, async块中的多个语句不是并行运行的。 async blocks essentially yield processor time to other processes while asynchronous requests are pending. 当异步请求处于挂起状态时, async块实质上会为其他进程提供处理器时间 So an async block won't generally run any faster than an equivalent sequence of synchronous operations, but it will allow more work to occur overall. 因此, async块通常不会比等效的同步操作序列运行得更快,但它将允许更多的工作总体发生。 If you're looking to run multiple statements in parallel, you'd be better off looking at the Task Parallel Library. 如果您希望并行运行多个语句,那么最好不要查看任务并行库。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM