我经常使用async/await和Task
,但从未使用过Task.Yield(),即使有所有的解释,我也不明白为什么需要这个方法。
有人能提供一个需要使用Yield()
的好例子吗?
我经常使用async/await和Task
,但从未使用过Task.Yield(),即使有所有的解释,我也不明白为什么需要这个方法。
有人能提供一个需要使用Yield()
的好例子吗?
async
/await
时,调用await FooAsync()
时,您调用的方法实际上可能不会异步运行。 内部实现可以自由地使用完全同步的路径返回。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
。
await Task.Yield()
强制方法变成异步,那么我们为什么还要编写“真正”的异步代码呢?想象一下一个很耗时的同步方法,只需在开头添加async
和await Task.Yield()
即可使其变为异步?那就几乎像是把所有同步代码包装到Task.Run()
中创建一个假的异步方法了。 - KrumelurTask.Run
来实现它,ExecuteFooOnUIThread
将在线程池上运行,而不是UI线程。通过await Task.Yield()
,你可以强制它以一种异步的方式运行,这样后续代码仍然在当前上下文中运行(只是在稍后的时间点)。这不是你通常会做的事情,但如果出于某些奇怪的原因需要这样做,那么这个选项是很好的。 - Reed CopseyExecuteFooOnUIThread()
运行非常漫长,它仍然会在某个时候阻塞UI线程并使UI无响应,这是正确的吗? - Krumelur在内部,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();
和原有代码有什么区别? - Erik Philipsvar dialogTask = await showAsync()
无法编译,因为await showAsync()
表达式不返回Task
(与没有await
时不同)。 也就是说,如果您使用await showAsync()
,则在对话框关闭后才会恢复执行,这就是它的不同之处。 这是因为window.ShowDialog
是同步API(尽管它仍然会泵送消息)。 在该代码中,我希望在对话框仍在显示时继续进行。 - noseratio - open to workawait Task.Yield()
和await Task.Delay(1)
有相同的效果吗? - David Klempfnerawait Task.Delay(1)
而不是 await Task.Yield()
? - noseratio - open to workTask.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) - Theodor Zouliasawait Task.Delay(1);
和 await Task.Yield();
做的事情几乎完全相同? - David Klempfnerawait Task.Delay(1)
让运行时来决定是否按顺序延迟执行而不放弃线程控制或者返回未完成的任务并放弃控制。另一方面,await Task.Yield()
确保执行上下文暂停当前线程的控制权。 - Joakim M. H.Task.Yield()
就像是async-await中Thread.Yield()
的对应物,但具有更加具体的条件。你甚至需要多少次Thread.Yield()
呢?我将首先广泛回答标题“何时使用Task.Yield()
”。以下条件都满足时,您会使用它:
这里的“异步上下文”指的是“同步上下文优先,然后是任务调度程序”。Stephen Cleary使用了这个术语。
Task.Yield()
大约做了this(许多帖子在此处或那里都有些错误):
await Task.Factory.StartNew(
() => {},
CancellationToken.None,
TaskCreationOptions.PreferFairness,
SynchronizationContext.Current != null?
TaskScheduler.FromCurrentSynchronizationContext():
TaskScheduler.Current);
Task.DefaultScheduler
中进行,通常使用 ConfigureAwait(false)
。相反,Task.Yield()
会给你一个不带 ConfigureAwait(bool)
的可等待对象。你需要使用粗略代码与 TaskScheduler.Default
。Task.Yield()
阻塞了队列,你需要重构你的代码,就像 noseratio 解释的那样。Task.Delay
。Task.Yield()
很小众,很容易被忽略。我只有一个虚构的例子,结合我的经验来解决一个由定制调度程序约束的异步餐 philosopher 问题。在我的多线程帮助库 InSync 中,它支持无序获取异步锁。如果当前的获取失败,则将异步获取排队。这里是代码 here。作为通用库,它需要 ConfigureAwait(false)
,因此我需要使用 Task.Factory.StartNew
。在一个闭源项目中,我的程序需要执行大量混合同步和异步代码,并具有
Task.Yield
与TaskScheduler.Current
没有任何关联。它会在当前的SynchronizationContext
上返回,而不是在当前的TaskScheduler
上。 - Theodor ZouliasTask.Yield
与 TaskScheduler.Current
有任何关联" 是什么意思。Task.Yield()
更喜欢在 SynchronizationContext.Current
上继续。如果 SynchronizationContext.Current
为 null,则继续将排队到原始队列。我不知道如何做与 Task.Yield()
相同的事情。最接近的方法,尽管仍然不同,就是上面的代码。这就是存在 Task.Yield()
的原因之一。 - keithyipSynchronizationContext.Current
为 null 时,在 TaskScheduler.Current
上安排继续。 - Theodor ZouliasYieldAwaitable
的源代码,我的看法是错误的。当没有 SynchronizationContext.Current
可以捕获时,Task.Yield
确实会捕获到 TaskScheduler.Current
。所以你对 Task.Yield
的近似是正确的。 - Theodor Zouliasprivate static async IAsyncEnumerable<Response> CreateAsyncEnumerable() {
await Task.Yield();
yield return new Response ("data");
}
await Task.Yield();
,它仍然可以工作,但会出现以下警告信息:
其他示例通常通过添加此异步方法缺少'await'操作符,将以同步方式运行。考虑使用'await'操作符等待非阻塞的API调用,或者使用'await Task.Run(...)'在后台线程上执行CPU密集型工作。
Task.Delay()
来模拟异步,但在测试中我宁愿不这样做。Task.Yield()
可以在模拟异步方法的实现中使用。
setTimeout(_, 0)
相等。 - nawfalBackgroundService.ExecuteAsync()
阻塞IHostedService.StartAsync()
,请参考https://blog.stephencleary.com/2020/05/backgroundservice-gotcha-startup.html和https://github.com/dotnet/runtime/issues/36063。 - n0099