我有一些用c#编写的执行并发代码的东西,大量使用任务并行库(Task and Future continuation chains)。
我现在将其移植到F#并试图找出使用F#Async工作流与TPL中的构造的优缺点。我倾向于TPL,但我认为无论哪种方式都可以。
有没有人有关于在F#中编写并发程序的提示和智慧来分享?
答案 0 :(得分:28)
这个名称几乎总结了差异:异步编程与并行编程。但是在F#你可以混合搭配。
当您希望异步执行代码时,F#异步工作流非常有用,即启动任务而不是等待最终结果。最常见的用法是IO操作。让你的线程坐在空闲循环中等待你的硬盘完成写入会浪费资源。
如果以异步方式开始写操作,则可以暂停该线程并稍后通过硬件中断唤醒它。
.NET 4.0中的任务并行库抽象出任务的概念 - 例如解码MP3或从数据库中读取一些结果。在这些情况下,您实际上需要计算结果,并在稍后的某个时间点等待操作的结果。 (通过访问.Result属性。)
您可以轻松地混合和匹配这些概念。比如在TPL Task对象中执行所有IO操作。对于程序员来说,你已经抽象出需要“处理”那个额外的线程,但是你在浪费资源。
同样,您可以创建一系列F#异步工作流并并行运行它们(Async.Parallel),但是您需要等待最终结果(Async.RunSynchronously)。这使您无需显式启动所有任务,但实际上您只是并行执行计算。
根据我的经验,我发现TPL更有用,因为通常我想并行执行N个操作。但是,当存在“幕后”的某些内容时,F#异步工作流是理想的,例如Reactive Agent或Mailbox类型的东西。 (您发送消息,处理它并将其发回。)
希望有所帮助。
答案 1 :(得分:2)
在4.0中我会说:
也可以混合搭配。他们添加了对将工作流作为任务运行并使用TaskFactory.FromAsync创建遵循异步开始/结束模式的任务的支持,TPL等效于Async.FromBeginEnd或Async.BuildPrimitive
。
let func() =
let file = File.OpenRead("foo")
let buffer = Array.zeroCreate 1024
let task1 = Task.Factory.FromAsync(file.BeginRead(buffer, 0, buffer.Length, null, null), file.EndRead)
task1.Start()
let task2 = Async.StartAsTask(file.AsyncRead(1024))
printfn "%d" task2.Result.Length
值得注意的是,Async Workflows运行时和TPL都将创建一个额外的内核原语(一个事件)并使用WaitForMultipleObjects来跟踪I / O完成,而不是使用完成端口和回调。在某些应用中这是不合需要的。