为什么在Task <t> monad中不包含CancellationToken?</t>

时间:2014-06-26 00:26:43

标签: c# f# task-parallel-library async-await monads

Task<T>巧妙地持有一个“已经开始,可能已经完成”的计算,它可以与其他任务组合,用函数映射等。相比之下,F#async monad持有“可以稍后开始,可能正在运行“计算,以及 a CancellationToken。在C#中,您通常必须通过CancellationToken遍历与Task一起使用的每个函数。为什么C#团队选择将计算包装在Task monad中,而不是CancellationToken

2 个答案:

答案 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,我必须明确地将tokentask2关联起来,以便通过将其作为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!