30

我有一些用 c# 编写的执行并发代码的东西,大量使用了任务并行库(任务和未来延续链)。

我现在将其移植到 F# 并试图找出使用 F# 异步工作流与 TPL 中的构造的优缺点。我倾向于 TPL,但我认为这两种方式都可以。

有没有人有关于在 F# 中编写并发程序的技巧和智慧来分享?

4

2 回答 2

30

这个名字几乎概括了不同之处:异步编程与并行编程。但在 F# 中,您可以混合搭配。

F# 异步工作流

当您希望代码异步执行时,F# 异步工作流很有帮助,即启动任务而不是等待最终结果。最常见的用法是 IO 操作。让您的线程处于空闲循环中等待您的硬盘完成写入会浪费资源。

如果您以异步方式开始写操作,您可以暂停线程并稍后通过硬件中断将其唤醒。

任务并行库

.NET 4.0 中的任务并行库抽象了任务的概念——例如解码 MP3,或从数据库中读取一些结果。在这些情况下,您实际上想要计算结果,并且在稍后的某个时间点等待操作的结果。(通过访问 .Result 属性。)

您可以轻松混合和匹配这些概念。比如在一个 TPL Task 对象中做你所有的 IO 操作。对于程序员来说,您已经抽象出“处理”额外线程的需要,但在幕后您正在浪费资源。

同样,您可以创建一系列 F# 异步工作流并并行运行它们 (Async.Parallel),但是您需要等待最终结果 (Async.RunSynchronously)。这使您无需显式启动所有任务,但实际上您只是在并行执行计算。

根据我的经验,我发现 TPL 更有用,因为通常我想并行执行 N 个操作。但是,当“幕后”发生某些事情(例如反应式代理或邮箱类型的事情)时,F# 异步工作流是理想的。(您发送消息,它会处理它并将其发回。)

希望有帮助。

于 2009-12-09T04:49:45.787 回答
2

在 4.0 中,我会说:

  • 如果您的函数是顺序的,请使用异步工作流。他们只是读得更好。
  • 将 TPL 用于其他一切。

也可以混合搭配。他们添加了对将工作流作为任务运行并使用TaskFactory.FromAsync创建遵循异步开始/结束模式的任务的支持,TPL 等效于Async.FromBeginEndAsync.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 完成,而不是使用完成端口和回调。这在某些应用中是不希望的。

于 2009-12-10T07:40:45.087 回答