有时候我们在代码中要执行一些非常耗时的操作, 我们不希望这些操作阻塞调用线程 (主线程) 的执行, 因为调用线程 (主线程) 可能还有更重要的工作要做, 我们希望将这些非常耗时的操作由另外一个线程去执行, 这个时候就可以用到 await Task.Yield(), 它借助了 C# 5.0 中的异步函数关键字 await async, 将 await 关键字之后的代码交由线程池中的另一个线程执行(前提是项目的 SynchronizationContext.Current 为 null).
那么有同学肯能会纳闷, await Task.Yield()和 await Task.CompletedTask 有什么不同吗?
它俩可大不一样
Task.CompletedTask 本质上来说是返回一个已经完成的 Task 对象, 所以这时如果我们用 await 关键字去等待 Task.CompletedTask,.NET Core 认为没有必要再去线程池启动一个新的线程来执行 await 关键字之后的代码, 所以实际上 await Task.CompletedTask 之前和之后的代码是在同一个线程上同步执行的, 通俗易懂的说就是单线程的. 这也是为什么很多文章说, 使用了 await async 关键字并不代表程序就变成异步多线程的了.
而 Task.Yield()就不一样了, 我们可以理解 Task.Yield()是真正使用 Task 来启动了一个线程, 只不过这个线程什么都没有干, 相当于在使用 await Task.Yield()的时候, 确实是在用 await 等待一个还没有完成的 Task 对象, 所以这时调用线程 (主线程) 就会立即返回去做其它事情了, 当调用线程 (主线程) 返回后, await 等待的 Task 对象就立即变为完成了, 这时 await 关键字之后的代码由另外一个线程池线程来执行.
下面我写一个示例代码来演示 await Task.Yield()和 await Task.CompletedTask 的不同:
- using System;
- using System.Runtime.CompilerServices;
- using System.Threading;
- using System.Threading.Tasks;
- namespace NetCoreTaskYield
- {
- class Program
- {
- /// <summary>
- /// TaskYield 使用 await Task.Yield(), 是真正的异步执行, await 关键字之前和之后的代码使用不同的线程执行
- /// </summary>
- static async Task TaskYield()
- {
- Console.WriteLine("TaskYield before await, current thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString());
- await Task.Yield();// 执行到 await Task.Yield()时, 调用 TaskYield()方法的线程 (主线程) 立即就返回了, await 关键字后面的代码实际上是由另一个线程池线程执行的
- // 注意 Task.Yield()方法返回的不是 Task 类的对象, 而是 System.Runtime.CompilerServices.YieldAwaitable 类的对象
- Console.WriteLine("TaskYield after await, current thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString());
- Thread.Sleep(3000);// 阻塞线程 3 秒钟, 模拟耗时的操作
- Console.WriteLine("TaskYield finished!");
- }
- /// <summary>
- /// 模拟 TaskYield 的异步执行
- /// </summary>
- static Task TaskYieldSimulation()
- {
- // 模拟 TaskYield()方法中, await 关键字之前的代码, 由调用 TaskYieldSimulation()方法的线程 (主线程) 执行
- Console.WriteLine("TaskYieldSimulation before await, current thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString());
- return Task.Run(() =>
- {
- // 使用 Task.Run 启动一个新的线程什么都不做, 立即完成, 相当于就是 Task.Yield()
- }).ContinueWith(t =>
- {
- // 下面模拟的是 TaskYield()方法中, await 关键字之后的代码, 由另一个线程池线程执行
- Console.WriteLine("TaskYieldSimulation after await, current thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString());
- Thread.Sleep(3000);// 阻塞线程 3 秒钟, 模拟耗时的操作
- Console.WriteLine("TaskYieldSimulation finished!");
- });
- }
- /// <summary>
- /// TaskCompleted 使用 await Task.CompletedTask, 是假的异步执行, 实际上是同步执行, await 关键字之前和之后的代码使用相同的线程执行
- /// </summary>
- static async Task TaskCompleted()
- {
- Console.WriteLine("TaskCompleted before await, current thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString());
- await Task.CompletedTask;// 执行到 await Task.CompletedTask 时, 由于 await 的 Task.CompletedTask 已经处于完成状态, 所以. NET Core 判定 await 关键字后面的代码还是由调用 TaskCompleted()方法的线程 (主线程) 来执行, 所以实际上整个 TaskCompleted()方法是单线程同步执行的
- Console.WriteLine("TaskCompleted after await, current thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString());
- Thread.Sleep(3000);// 阻塞线程 3 秒钟, 模拟耗时的操作
- Console.WriteLine("TaskCompleted finished!");
- }
- static void Main(string[] args)
- {
- Console.WriteLine("Main thread id: {0}", Thread.CurrentThread.ManagedThreadId.ToString());
- Console.WriteLine("==============================================");
- TaskYield().Wait();
- Console.WriteLine("==============================================");
- TaskCompleted().Wait();
- Console.WriteLine("==============================================");
- TaskYieldSimulation().Wait();
- Console.WriteLine("Press any key to end...");
- Console.ReadKey();
- }
- }
- }
执行结果如下所示:
注意 TaskYield()方法是真正的异步执行, TaskYieldSimulation()方法模拟演示了 await Task.Yield()异步执行的原理, 而 TaskCompleted()方法是假的异步执行, 实则为同步单线程执行.
参考文献:
终于明白了 C# 中 Task.Yield 的用途
await Task.Yield(); 超简单理解!
来源: https://www.cnblogs.com/OpenCoder/p/12201446.html