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

Seb*_*ood 13 c# monads f# task-parallel-library async-await

Task<T>整齐持有"已经开始,可能会完成"计算,可与其它任务,与功能等.相比之下映射组成,F#的async单子持有"以后可以开始,可能现在正在运行"的计算,连同一个CancellationToken.在C#中,您通常必须CancellationToken遍历每个使用a的函数Task.为什么C#团队选择将计算包装在Taskmonad中,但不是CancellationToken

nos*_*tio 10

或多或少,它们封装了CancellationTokenC#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
Run Code Online (Sandbox Code Playgroud)

对于非异步lambda,我必须通过将它作为参数提供给(或),明确地tokentask2for 关联取消传播.对于与之一起使用的lambda ,它会自动作为基础结构代码的一部分发生.new Task()Task.Runasynctask3async/await

此外,任何方法 token都会传播async方法的取消,而对于非异步计算new Task()/ Task.Runlambda,它必须是传递给任务构造函数的相同标记或Task.Run.

当然,我们仍然需要token.ThrowIfCancellationRequested()手动调用以实现协作取消模式.我不能回答为什么C#和TPL团队决定以这种方式实现它,但我猜他们的目的是不要使语法过于复杂,async/await但要保持足够的灵活性.

至于F#,我没有看过生成的异步工作流的IL代码,如Tomas Petricek在你链接的博客文章中所述.然而,据我所知,令牌仅在工作流的某些位置自动测试,这些位置与awaitC#相对应(通过类比,我们可能会token.ThrowIfCancellationRequested()在每次awaitC#之后手动调用).这意味着任何CPU绑定的工作仍然不会立即取消.否则,F#必须token.ThrowIfCancellationRequested()在每个IL指令之后发出,这将是相当大的开销.

  • @GregC,我宁愿将`IEnumerable`与'IObservable`(Rx的一个特性,而不是TPL的`Task`)进行比较. (2认同)