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()
一个伪造的异步方法一样。