Task<T>
巧妙地持有一个“已经开始,可能已经完成”的计算,它可以与其他任务组合,用函数映射等。相比之下,F#async
monad持有“可以稍后开始,可能正在运行“计算,以及 a CancellationToken
。在C#中,您通常必须通过CancellationToken
遍历与Task
一起使用的每个函数。为什么C#团队选择将计算包装在Task
monad中,而不是CancellationToken
?
答案 0 :(得分:9)
或多或少,它们封装了CancellationToken
对C#async
方法的隐式使用。考虑一下:
var cts = new CancellationTokenSource();
cts.Cancel();
var token = cts.token;
var task1 = new Task(() => token.ThrowIfCancellationRequested());
task1.Start();
task1.Wait(); // task in Faulted state
var task2 = new Task(() => token.ThrowIfCancellationRequested(), token);
task2.Start();
task2.Wait(); // task in Cancelled state
var task3 = (new Func<Task>(async() => token.ThrowIfCancellationRequested()))();
task3.Wait(); // task in Cancelled state
对于非异步lambda,我必须明确地将token
与task2
关联起来,以便通过将其作为new Task()
(或{{1 }})。对于与Task.Run
一起使用的async
lambda,它会自动作为task3
基础结构代码的一部分发生。
此外,任何 async/await
都会传播token
方法的取消,而对于非异步计算async
/ new Task()
lambda则有是传递给任务构造函数或Task.Run
的相同的标记。
当然,我们仍然需要手动调用Task.Run
来实现合作取消模式。我不能回答为什么C#和TPL团队决定以这种方式实现它,但我猜他们的目的是不要使token.ThrowIfCancellationRequested()
的语法过于复杂,但要保持足够的灵活性。
至于F#,我还没有看到异步工作流生成的IL代码,如你链接的Tomas Petricek blog post所示。然而,据我所知,令牌仅在工作流的某些位置自动测试,与C#中的async/await
相对应(通过类比,我们可能在每{{1}后手动调用await
在C#中。这意味着任何受CPU限制的工作仍然无法立即取消。否则,F#必须在每条IL指令后发出token.ThrowIfCancellationRequested()
,这将是相当大的开销。
答案 1 :(得分:2)
任务最初是为了在类中包含其他功能,但后来更改为聚合具有附加功能支持的对象。一切都以表现为名。
http://blogs.msdn.com/b/pfxteam/archive/2011/11/10/10235962.aspx(参见&#34;重组任务&#34;在论文中)Joseph E. Hoag撰写的论文对.NET 4.5中的优化提供了很好的见解。我相信对于那些试图从async / await中挤出最后10%性能的人来说,这是值得一读的。
我认为在决定如何打包取消功能时会应用类似的思维过程。
我无法代表C#或BCL团队发言,但我认为这是一项性能优化,要么只能在F#编译器中使用,要么性能对F#团队来说无关紧要。 SRP, baby!