我什么时候会使用Task.Yield()?

时间:2014-03-25 20:00:09

标签: c# async-await

我经常使用async / await和Task,但从未使用Task.Yield(),说实话,即使有了所有的解释,我也不明白为什么我需要这种方法。

有人可以提供一个需要Yield()的好例子吗?

5 个答案:

答案 0 :(得分:189)

使用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

答案 1 :(得分:27)

如果await Task.Yield()SynchronizationContext.Currentnull在内部,只会在当前同步上下文或随机池线程上对延续进行排队。

efficiently implemented作为自定义等待者。产生相同效果的效率较低的代码可能就像这样简单:

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

另见:

答案 2 :(得分:3)

Task.Yield()的一种用法是在进行异步递归时防止堆栈溢出。 Task.Yield()阻止同步继续。

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

答案 3 :(得分:0)

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() 非常小众,很容易被躲避。结合我的经验,我只有一个虚构的例子。它是为了解决一个受自定义调度程序约束的异步用餐哲学家问题。在我的多线程辅助库 InSync 中,它支持异步锁的无序获取。如果当前获取失败,它会将异步获取排入队列。代码是here。它需要 ConfigureAwait(false) 作为通用库,所以我需要使用 Task.Factory.StartNew。在一个闭源项目中,我的程序需要执行重要的同步代码和异步代码与

  • 半实时工作的高线程优先级
  • 某些后台工作的低线程优先级
  • UI 的普通线程优先级

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

答案 4 :(得分:-4)

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