我使用async/await和任务很多,但从来没有使用Task. yield(),说实话,即使有所有的解释,我也不明白为什么我需要这个方法。

有人能给出一个需要Yield()的好例子吗?


当你使用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.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;

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与适当的任务调度程序替换。

参见:

"await Task.Yield()"及其替代方法 的任务。产量-实际用途?


Task.Yield()可用于异步方法的模拟实现。


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();
    }

Task.Yield()类似于async-await中的Thread.Yield(),但具有更具体的条件。你需要多少次Thread.Yield()?我将首先大致回答“何时使用Task.Yield()”这个问题。当你满足以下条件时,你就可以:

希望将控件返回到异步上下文(建议任务调度器先执行队列中的其他任务) 需要在异步上下文中继续 最好在任务调度程序空闲时立即继续 不想被取消 更喜欢短代码

这里的术语“异步上下文”意味着“先同步上下文,然后是TaskScheduler”。斯蒂芬·克利里用过。

Task.Yield()大约是这样做的(许多帖子在这里和那里都有轻微的错误):

await Task.Factory.StartNew( 
    () => {}, 
    CancellationToken.None, 
    TaskCreationOptions.PreferFairness,
    SynchronizationContext.Current != null?
        TaskScheduler.FromCurrentSynchronizationContext(): 
        TaskScheduler.Current);

如果其中任何一个条件被打破,您需要使用其他替代方案。

如果任务的继续应在任务中。DefaultScheduler,你通常使用ConfigureAwait(false)。相反,Task.Yield()给你一个没有ConfigureAwait(bool)的可等待对象。你需要使用TaskScheduler.Default的近似代码。

如果Task.Yield()阻塞了队列,则需要按照noseratio的解释重新构造代码。

如果您需要在更晚的时间进行延续,例如,以毫秒为单位,则可以使用Task.Delay。

如果您希望在队列中取消任务,但不想检查取消令牌,也不想自己抛出异常,则需要使用带有取消令牌的近似代码。

Task.Yield() is so niche and easily dodged. I only have one imaginary example by mixing my experience. It is to solve an async dining philosopher problem constrained by a custom scheduler. In my multi-thread helper library InSync, it supports unordered acquisitions of async locks. It enqueues an async acquisition if the current one failed. The code is here. It needs ConfigureAwait(false) as a general purpose library so I need to use Task.Factory.StartNew. In a closed source project, my program needs to execute significant synchronous code mixed with async code with

用于半实时工作的高线程优先级 一些后台工作的低线程优先级 UI的正常线程优先级

因此,我需要一个自定义调度器。我可以很容易地想象到,一些糟糕的开发人员需要以某种方式将同步和异步代码与一些特殊的调度程序混合在一起(一个平行宇宙可能不包含这样的开发人员);但是为什么他们不使用更健壮的近似代码,这样他们就不需要写一个冗长的注释来解释为什么和它做了什么?