我有点了解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() }
Run Code Online (Sandbox Code Playgroud)
在F#专家书(和许多其他来源),他们说喜欢
让!var = expr只是表示"执行异步操作expr并在操作完成时将结果绑定到var.然后继续执行其余的计算主体"
我也知道在执行异步操作时会创建一个新线程.我最初的理解是异步操作后有两个并行线程,一个执行I/O,另一个继续同时执行异步主体.
但在这个例子中,我很困惑
let! resp = req.AsyncGetResponse()
let stream = resp.GetResponseStream()
Run Code Online (Sandbox Code Playgroud)
如果resp
尚未启动并且异步体中的线程想要发生GetResponseStream
什么?这可能是错误吗?
也许我原来的理解是错误的.F#专家书中引用的句子实际上意味着"创建一个新线程,挂起当前线程,当新线程完成时,唤醒正文线程并继续",但在这种情况下我看不到我们可以保存随时.
在最初的理解中,当一个异步块中有多个独立的 IO操作时,可以节省时间,这样它们可以在不相互干预的情况下同时完成.但是在这里,如果我没有得到回复,我就无法创建流; 只有我有流,我可以开始阅读流.获得的时间在哪里?
Bri*_*ian 23
此示例中的"异步"不是关于并发或节省时间,而是关于提供良好的编程模型而不阻塞(读取:浪费)线程.
如果使用其他编程语言,通常有两种选择:
您可以阻止,通常通过调用同步方法.缺点是线程在等待磁盘或网络I/O或您拥有的东西时被占用并且没有做任何有用的工作.优点是代码简单(普通代码).
您可以使用回调来异步调用,并在操作完成时收到通知.优点是您不会阻塞线程(这些线程可以返回到例如ThreadPool,并且当操作完成后将使用新的ThreadPool线程来回调).缺点是一个简单的代码块被分成一堆回调方法或lambda,并且很快就会在回调中维护状态/控制流/异常处理变得非常复杂.
所以你在岩石和坚硬的地方之间; 你要么放弃简单的编程模型,要么浪费线程.
F#模型提供了两全其美的效果; 你不会阻止线程,但你保持简单的编程模型.像这样的构造let!
使您能够在异步块的中间"跳线",所以在代码中
Blah1()
let! x = AsyncOp()
Blah2()
Run Code Online (Sandbox Code Playgroud)
Blah1
例如,ThreadPool线程#13可以运行,但随后AsyncOp会将该线程释放回ThreadPool.稍后当AsyncOp完成时,其余代码将在可用线程(可能是ThreadPool线程#20)上开始备份,该线程绑定x
到结果然后运行Blah2
.在简单的客户端应用程序中,这很少重要(除非确保您不阻止UI线程),但在执行I/O的服务器应用程序中(线程通常是宝贵的资源 - 线程很昂贵,您不能浪费它们阻塞)非阻塞I/O通常是使应用程序扩展的唯一方法.F#使您能够编写非阻塞I/O,而无需将程序降级为大量的意大利面条代码回调.
也可以看看
http://cs.hubfs.net/forums/thread/8262.aspx
我认为理解异步工作流最重要的是它们的顺序与用F#编写的普通代码(或C#,就此而言)是顺序的相同.你有一些let
以通常顺序和一些表达式(可能有副作用)评估的绑定.实际上,异步工作流通常看起来更像命令式代码.
异步工作流的第二个重要方面是它们是非阻塞的.这意味着您可以执行以某种非标准方式执行的操作,并且在执行时不会阻塞该线程.(通常,let!
在F#计算表达式中始终表示存在一些非标准行为 - 如果不在Maybe monad中生成结果,则可能会失败,或者它可能是异步工作流的非阻塞执行).
从技术上讲,非阻塞执行是通过注册一些将在操作完成时触发的回调来实现的.相对简单的示例是等待某个指定时间的异步工作流 - 这可以使用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() )
Run Code Online (Sandbox Code Playgroud)
还有几种方法可以将F#异步工作流用于并行或并发编程,但这些只是F#工作流或基于它们构建的库的更复杂用法 - 它们利用了前面描述的非阻塞行为.
您可以使用StartChild
在后台启动工作流 - 该方法为您提供了一个正在运行的工作流,您可以let!
在工作流中稍后使用(使用)等待完成,同时您可以继续执行其他操作.这类似于.NET 4.0中的任务,但它以异步方式运行,因此更适合I/O操作.
您可以使用Async.Parallel
创建多个工作流并等待所有工作流完成(这对于数据并行操作非常有用).这类似于PLINQ,但是再次,async
如果你做一些I/O操作会更好.
最后,您可以使用MailboxProcessor
允许您使用消息传递样式(Erlang样式)编写并发应用程序.对于许多问题,这是线程的一个很好的替代方案.
归档时间: |
|
查看次数: |
3430 次 |
最近记录: |