我有一些用 c# 编写的执行并发代码的东西,大量使用了任务并行库(任务和未来延续链)。
我现在将其移植到 F# 并试图找出使用 F# 异步工作流与 TPL 中的构造的优缺点。我倾向于 TPL,但我认为这两种方式都可以。
有没有人有关于在 F# 中编写并发程序的技巧和智慧来分享?
我有一些用 c# 编写的执行并发代码的东西,大量使用了任务并行库(任务和未来延续链)。
我现在将其移植到 F# 并试图找出使用 F# 异步工作流与 TPL 中的构造的优缺点。我倾向于 TPL,但我认为这两种方式都可以。
有没有人有关于在 F# 中编写并发程序的技巧和智慧来分享?
这个名字几乎概括了不同之处:异步编程与并行编程。但在 F# 中,您可以混合搭配。
当您希望代码异步执行时,F# 异步工作流很有帮助,即启动任务而不是等待最终结果。最常见的用法是 IO 操作。让您的线程处于空闲循环中等待您的硬盘完成写入会浪费资源。
如果您以异步方式开始写操作,您可以暂停线程并稍后通过硬件中断将其唤醒。
.NET 4.0 中的任务并行库抽象了任务的概念——例如解码 MP3,或从数据库中读取一些结果。在这些情况下,您实际上想要计算结果,并且在稍后的某个时间点等待操作的结果。(通过访问 .Result 属性。)
您可以轻松混合和匹配这些概念。比如在一个 TPL Task 对象中做你所有的 IO 操作。对于程序员来说,您已经抽象出“处理”额外线程的需要,但在幕后您正在浪费资源。
同样,您可以创建一系列 F# 异步工作流并并行运行它们 (Async.Parallel),但是您需要等待最终结果 (Async.RunSynchronously)。这使您无需显式启动所有任务,但实际上您只是在并行执行计算。
根据我的经验,我发现 TPL 更有用,因为通常我想并行执行 N 个操作。但是,当“幕后”发生某些事情(例如反应式代理或邮箱类型的事情)时,F# 异步工作流是理想的。(您发送消息,它会处理它并将其发回。)
希望有帮助。
在 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
还值得注意的是,异步工作流运行时和 TPL 都将创建一个额外的内核原语(一个事件)并使用WaitForMultipleObjects来跟踪 I/O 完成,而不是使用完成端口和回调。这在某些应用中是不希望的。