缺少非捕获Task.Yield迫使我使用Task.Run,​​为什么要这样做呢?

如果这个问题是基于意见的,请提前道歉。 这里已经讨论了缺少不捕获执行上下文的Task.Yield版本。 显然,此function在早期版本的Async CTP中以某种forms出现,但由于它很容易被滥用而被删除

IMO,这样的function可能像Task.Run本身一样容易被滥用。 这就是我的意思。 想象一下,有一个等待的SwitchContext.Yield API来调度ThreadPool上的延续,因此执行将始终在与调用线程不同的线程上继续。 我可以在下面的代码中使用它,它从UI线程启动一些CPU绑定的工作。 我认为这是一种在池线程上继续CPU绑定工作的便捷方式:

 class Worker { static void Log(string format, params object[] args) { Debug.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, String.Format(format, args)); } public async Task UIAction() { // UI Thread Log("UIAction"); // start the CPU-bound work var cts = new CancellationTokenSource(5000); var workTask = DoWorkAsync(cts.Token); // possibly await for some IO-bound work await Task.Delay(1000); Log("after Task.Delay"); // finally, get the result of the CPU-bound work int c = await workTask; Log("Result: {0}", c); } async Task DoWorkAsync(CancellationToken ct) { // start on the UI thread Log("DoWorkAsync"); // switch to a pool thread and yield back to the UI thread await SwitchContext.Yield(); Log("after SwitchContext.Yield"); // continue on a pool thread int c = 0; while (!ct.IsCancellationRequested) { // do some CPU-bound work on a pool thread: counting cycles :) c++; // and use async/await too await Task.Delay(50); } return c; } } 

现在,如果没有SwitchContext.YieldDoWorkAsync将如下所示。 它以异步委托和任务嵌套的forms增加了一些额外的复杂性:

 async Task DoWorkAsync(CancellationToken ct) { // start on the UI thread Log("DoWorkAsync"); // Have to use async delegate // Task.Run uwraps the inner Task task return await Task.Run(async () => { // continue on a pool thread Log("after Task.Yield"); int c = 0; while (!ct.IsCancellationRequested) { // do some CPU-bound work on a pool thread: counting cycles :) c++; // and use async/await too await Task.Delay(50); } return c; }); } 

也就是说,实现SwitchContext.Yield实际上可能非常简单并且(我敢说)高效:

 public static class SwitchContext { public static Awaiter Yield() { return new Awaiter(); } public struct Awaiter : System.Runtime.CompilerServices.INotifyCompletion { public Awaiter GetAwaiter() { return this; } public bool IsCompleted { get { return false; } } public void OnCompleted(Action continuation) { ThreadPool.QueueUserWorkItem((state) => ((Action)state)(), continuation); } public void GetResult() { } } } 

所以, 我的问题是 ,为什么我更喜欢第一个版本的DoWorkAsync而不是第一个版本,为什么使用SwitchContext.Yield会被认为是一种不好的做法?

您不必将Task.Run放在DoWorkAsync 。 考虑这个选项:

 public async Task UIAction() { // UI Thread Log("UIAction"); // start the CPU-bound work var cts = new CancellationTokenSource(5000); var workTask = Task.Run(() => DoWorkAsync(cts.Token)); // possibly await for some IO-bound work await Task.Delay(1000); Log("after Task.Delay"); // finally, get the result of the CPU-bound work int c = await workTask; Log("Result: {0}", c); } 

这导致代码具有更清晰的意图。 DoWorkAsync是一种自然同步的方法,因此它具有同步签名 。 DoWorkAsync既不知道也不关心UI。 关注UI线程的UIAction使用Task.Run将工作推送到后台线程。

作为一般规则,尝试尽可能“推”任何Task.Run调用库方法。