C# 为什么不是';t任务中包含的CancellationToken<;T>;蒙纳德?
C# 为什么不是';t任务中包含的CancellationToken<;T>;蒙纳德?,c#,f#,task-parallel-library,async-await,monads,C#,F#,Task Parallel Library,Async Await,Monads,Task巧妙地保存了一个“has start,maybe finished”计算,该计算可以与其他任务组成,映射函数等。相反,F#monad保存了一个“可能稍后启动,可能正在运行”计算,以及一个CancellationToken。在C#中,您通常必须将CancellationToken线程化到与任务一起工作的每个函数中。为什么C#团队选择将计算封装在任务单子中,而不是取消令牌?任务最初用于包含类中的附加功能,但后来被更改为使用附加功能支持聚合对象。这一切都是为了表现 (参见论文中的“重组任务”)
Task
巧妙地保存了一个“has start,maybe finished”计算,该计算可以与其他任务组成,映射函数等。相反,F#monad保存了一个“可能稍后启动,可能正在运行”计算,以及一个CancellationToken
。在C#中,您通常必须将CancellationToken
线程化到与任务一起工作的每个函数中。为什么C#团队选择将计算封装在任务
单子中,而不是取消令牌
?任务最初用于包含类中的附加功能,但后来被更改为使用附加功能支持聚合对象。这一切都是为了表现
(参见论文中的“重组任务”)Joseph E.Hoag的论文对.NET 4.5中的优化提供了很好的见解。我相信,对于任何试图从async/Wait中挤出最后10%的性能的人来说,这本书都是值得一读的
我假设在决定如何打包取消功能时也应用了类似的思维过程
我不能代表C#或团队说话,但我认为这是一种性能优化,要么只能在F#编译器中实现,要么性能对F#团队无关紧要 或多或少,他们封装了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
var cts=new CancellationTokenSource();
cts.Cancel();
var-token=cts.token;
var task1=新任务(()=>token.ThrowIfCancellationRequested());
task1.Start();
task1.Wait();//任务处于错误状态
var task2=新任务(()=>token.ThrowIfCancellationRequested(),token);
task2.Start();
task2.Wait();//处于取消状态的任务
var task3=(新函数(异步()=>token.ThrowIfCancellationRequested())();
task3.Wait();//处于取消状态的任务
对于非异步lambda,我必须显式地将令牌
与task2
关联,以便通过将其作为参数提供给newtask()
(或Task.Run
)来正确传播取消。对于与task3
一起使用的async
lambda,它作为async/await
基础结构代码的一部分自动发生
此外,对于异步方法,任何标记都会传播取消,而对于非异步计算新任务()
/任务。Run
lambda,它必须是传递给任务构造函数或任务的相同标记。Run
当然,我们仍然需要手动调用token.ThrowIfCancellationRequested()
来实现协作取消模式。我无法回答为什么C#和TPL团队决定以这种方式实现它,但我想他们的目的是不使async/await
的语法过于复杂,同时保持足够的灵活性
至于F#,我还没有看过异步工作流生成的IL代码,如Tomas Petricek的《you linked》中所示。然而,据我所知,令牌仅在工作流的某些位置自动测试,这些位置对应于C#中的await
(通过类比,我们可能在C#中的每次await
之后手动调用token.throwifccancellationrequest()
)。这意味着任何CPU限制的工作仍然不会立即取消。否则,F#必须在每个IL指令之后发出令牌。throwifcancellationrequest()
,这将是相当大的开销。在C#中,您有“可以稍后启动,现在可能正在运行”计算,以及var task=new task(操作,令牌)形式的CancellationToken选项
。我忘了那张白纸了。驱动器似乎是为了使任务对象本身尽可能小,因此没有携带取消令牌,而是在由async/await生成的状态机中携带它。何时以及为什么要使用async/await进行CPU绑定的工作?@GregC,任何时候我需要在UI应用程序中执行CPU限制的工作:var pi=wait Task.Run(()=>CalcPi(digits,token),token)
@GregC作为并行化的一种形式<代码>等待任务。当所有(任务)
@GregC时,我宁愿将IEnumerable
与IObservable
(Rx的一项功能,与TPL的任务
)进行比较。我想链接Thomas P的一个例外解释