await TaskYield 的意义
2024-03-17 17:33:41

await Task.Yield()的意义就是一句话:释放控制权,让其他任务得到执行的机会。
为什么要这么做呢?主要是 为了让调用者没有 await 的时候可以立即返回

考虑以下例子:

1
2
3
4
5
6
7
8
9
10
11
12
async Task Test()
{
Thread.Sleep(1000); // 模拟耗时操作

await Bar(); // 释放控制权
}

async Task Foo()
{
_ = Test(); // 不需要等待 Test 执行完成,而是希望尽快返回
Console.WriteLine("Called Test");
}

因为 Test 方法开头有一段耗时的操作,会阻塞 Foo 一秒钟。直到碰上await释放了控制权,此时由于调用方并没有用await去等待 Test 方法,所以 Foo 才得以继续执行。

问题是什么呢?问题在于 Foo 希望立即返回,而你(Test方法)却阻塞了我一秒钟!
作为异步 API 的设计者,那么就可以在函数开头加上一句await Task.Yield()

1
2
3
4
5
6
7
8
async Task Test()
{
await Task.Yield(); // 立即释放控制权

Thread.Sleep(1000); // 由于 await 之后重新分配到了新线程上执行,所以不会阻塞调用线程

await Bar();
}

这就实现了一个真正的异步方法!但注意这里有个前提,就是 调用者没有使用 await 去等待时才有意义

为什么不用 Task.CompletedTask?

从前面的内容来看,要实现一个能立即返回的异步方法,重点就是要尽可能快的调用await以达到释放控制权的目的。
那么我们可以去await框架内部的一些特殊的任务,比如 Task.CompletedTask

1
2
3
4
5
6
async Task Test1()
{
await Task.CompletedTask; // 因为是一个已完成的任务,所以不会释放控制权

Thread.Sleep(1000); // 依然在调用线程上执行,导致阻塞
}

Task.CompletedTask是一个已经完成的 Task 对象,它表示一个不返回任何结果的已完成任务。它会立即返回,并且不会引发线程切换。因为任务已经完成,不需要等待。
所以它和Task.Yield不是一回事。

Task.Delay(0) 呢?

1
2
3
4
5
6
async Task Test1()
{
await Task.Delay(0); // 等同于 Task.CompletedTask

Thread.Sleep(1000); // 依然在调用线程上执行,导致阻塞
}

Task.Delay 实现中,延迟时间为 0 时将会返回Task.CompletedTask

参考

When would I use Task.Yield()?
C#/.NET 中 Thread.Sleep(0), Task.Delay(0), Thread.Yield(), Task.Yield() 不同的执行效果和用法建议
await Task.Yield()和await Task.CompletedTask有什么不同
终于明白了 C# 中 Task.Yield 的用途
Task.Delay(0) not asynchronous
Task.Yield() versus Task.Delay(0)