Answers:
当使用async/时await,不能保证在调用时所调用的方法await FooAsync()实际上将异步运行。内部实现可以使用完全同步的路径自由返回。
如果要在不阻塞的关键位置创建API,并且异步运行一些代码,并且被调用的方法有可能会同步运行(有效地阻塞),则使用await Task.Yield()将强制您的方法异步并返回在那个时候控制。其余代码将在以后的上下文中在稍后的时间执行(此时,它仍然可以同步运行)。
如果您创建一个需要一些“长时间运行”初始化的异步方法,则这也很有用,即:
private async void button_Click(object sender, EventArgs e)
{
await Task.Yield(); // Make us async right away
var data = ExecuteFooOnUIThread(); // This will run on the UI thread at some point later
await UseDataAsync(data);
}
在没有Task.Yield()调用的情况下,该方法将一直同步执行,直到第一次调用为止await。
Task.Run来实现,ExecuteFooOnUIThread它将运行在线程池上,而不是UI线程上。使用await Task.Yield(),您可以强制其异步,以使后续代码仍在当前上下文上运行(只是在稍后的时间点)。这不是您通常要做的,但是如果出于某些奇怪的原因需要此选项,则很好。
ExecuteFooOnUIThread()运行时间很长,它仍然会在某个时候长时间阻塞UI线程并使UI无响应,这是正确的吗?
在内部,await Task.Yield()仅在当前同步上下文或随机池线程(如果SynchronizationContext.Current是)上对继续进行排队null。
它可以有效地实现为自定义的等待者。产生相同效果的效率较低的代码可能像这样简单:
var tcs = new TaskCompletionSource<bool>();
var sc = SynchronizationContext.Current;
if (sc != null)
sc.Post(_ => tcs.SetResult(true), null);
else
ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(true));
await tcs.Task;
Task.Yield()可以用作某些怪异的执行流程变更的捷径。例如:
async Task DoDialogAsync()
{
var dialog = new Form();
Func<Task> showAsync = async () =>
{
await Task.Yield();
dialog.ShowDialog();
}
var dialogTask = showAsync();
await Task.Yield();
// now we're on the dialog's nested message loop started by dialog.ShowDialog
MessageBox.Show("The dialog is visible, click OK to close");
dialog.Close();
await dialogTask;
// we're back to the main message loop
}
就是说,我想不出Task.Yield()无法用Task.Factory.StartNew适当的任务调度程序代替的情况。
也可以看看:
var dialogTask = await showAsync();?
var dialogTask = await showAsync()将不会编译,因为该await showAsync()表达式不会返回a Task(与不使用则不同await)。就是说,如果您这样做await showAsync(),则仅在关闭对话框之后才可以继续执行它,这就是不同之处。这是因为它window.ShowDialog是一个同步API(尽管它仍然会泵送消息)。在该代码中,我想继续显示对话框。
一种用途Task.Yield()是在进行异步递归时防止堆栈溢出。Task.Yield()防止同步继续。但是请注意,这可能导致OutOfMemory异常(如Triynko所述)。无休止的递归仍然不安全,您最好将递归重写为循环。
private static void Main()
{
RecursiveMethod().Wait();
}
private static async Task RecursiveMethod()
{
await Task.Delay(1);
//await Task.Yield(); // Uncomment this line to prevent stackoverlfow.
await RecursiveMethod();
}
await Task.Delay(1)足以阻止它。(控制台应用程序,.NET Core 3.1,C#8)
Task.Yield() 可以在异步方法的模拟实现中使用。
await Task.Yield()强制该方法异步,为什么还要麻烦编写“真实的”异步代码?想象一下繁重的同步方法。要使其异步,只需添加,async然后await Task.Yield()在开头神奇地将其异步?这几乎就像将所有同步代码包装成Task.Run()一个伪造的异步方法一样。