了解 F# 异步编程 [英] Understanding F# Asynchronous Programming

查看:21
本文介绍了了解 F# 异步编程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有点了解 F# 中异步编程的语法.例如

let downloadUrl(url:string) = async {让 req = HttpWebRequest.Create(url)//异步运行操作让!resp = req.AsyncGetResponse()让流 = resp.GetResponseStream()//完成后处理 'StreamReader'使用 reader = new StreamReader(stream)//异步运行,然后返回结果返回!reader.AsyncReadToEnd() }

在 F# 专家书籍(和许多其他来源)中,他们说像

<块引用>

让!var = expr 仅表示执行异步操作expr 并在操作完成时将结果绑定到 var.然后继续执行计算体的其余部分"

我也知道在执行异步操作时会创建一个新线程.我原来的理解是async操作之后有两个并行线程,一个做I/O,一个同时继续执行async body.

但在这个例子中,我很困惑

 让!resp = req.AsyncGetResponse()让流 = resp.GetResponseStream()

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

所以也许我原来的理解是错误的.F#专家书中引用的那句话其实是创建一个新线程,挂起当前线程,当新线程结束时,唤醒主体线程并继续",但在这种情况下我看不到我们可以保存随时.

原来的理解是在一个async block中有多个独立 IO操作时节省了时间,这样它们就可以同时完成而无需相互干预.但是在这里,如果我没有得到响应,我就无法创建流;只有我有流,我可以开始阅读流.哪来的时间?

解决方案

这个例子中的异步"不是关于并发或节省时间,而是关于提供一个不阻塞(读取:浪费)线程的良好编程模型.

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

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

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

所以你在岩石和坚硬的地方之间;你要么放弃简单的编程模型,要么浪费线程.

F# 模型提供了两全其美的优点;你不会阻塞线程,但你会保持简单的编程模型.像 let! 这样的结构使你能够在异步块的中间线程跳跃",所以在像

这样的代码中

Blah1()让!x = AsyncOp()废话2()

Blah1 可能会在 ThreadPool 线程 #13 上运行,但是 AsyncOp 会将该线程释放回 ThreadPool.稍后,当 AsyncOp 完成时,其余代码将在一个可用线程(例如,ThreadPool 线程 #20)上开始备份,该线程将 x 绑定到结果,然后运行 ​​Blah2.在微不足道的客户端应用程序中,这很少重要(除非确保您不阻塞 UI 线程),但在执行 I/O 的服务器应用程序中(线程通常是宝贵的资源 - 线程很昂贵,您不能通过以下方式浪费它们)阻塞)非阻塞 I/O 通常是使应用程序扩展的唯一方法.F# 使您能够编写非阻塞 I/O,而不会让程序退化为大量意大利面条式代码回调.

另见

使用异步工作流进行并行化的最佳实践

如何在 F# 中进行链式回调?

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

I kind of know the syntax of asynchronous programming in F#. E.g.

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

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"

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.

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? 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.

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. 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. 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 e.g. to the ThreadPool and a new ThreadPool thread will be used when the operation completes to call you back). 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.

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; you don't block threads, but you keep the straightforward programming model. Constructs like 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. 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. 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. F# enables you to write non-blocking I/O without having the program degrade into a mass of spaghetti-code callbacks.

See also

Best practices to parallelize using async workflow

How to do chained callbacks in F#?

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

这篇关于了解 F# 异步编程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆