170

I'm using async/await and Task a lot but have never been using Task.Yield() and to be honest even with all the explanations I do not understand why I would need this method.

Can somebody give a good example where Yield() is required?

3 답변


173

When you use async/await, there is no guarantee that the method you call when you do await FooAsync() will actually run asynchronously. The internal implementation is free to return using a completely synchronous path.

If you're making an API where it's critical that you don't block and you run some code asynchronously, and there's a chance that the called method will run synchronously (effectively blocking), using await Task.Yield() will force your method to be asynchronous, and return control at that point. The rest of the code will execute at a later time (at which point, it still may run synchronously) on the current context.

This can also be useful if you make an asynchronous method that requires some "long running" initialization, ie:

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

Without the Task.Yield() call, the method will execute synchronously all the way up to the first call to await.


  • I feel like I'm misinterpreting something here. If await Task.Yield() forces the method to be async, why would we bother writing "real" async code? Imagine a heavy sync method. To make it async, just add async and await Task.Yield() in the beginning and magically, it will be async? That would pretty much be like wrapping all sync code into Task.Run() and create a fake async method. - Krumelur
  • @Krumelur There's a big difference - look at my example. If you use a Task.Run to implement it, ExecuteFooOnUIThread will run on the thread pool, not the UI thread. With await Task.Yield(), you force it to be asynchronous in a way that the subsequent code is still run on the current context (just at a later point in time). It's not something you'd normally do, but it is nice that there is the option if it's required for some strange reason. - Reed Copsey
  • One more question: if ExecuteFooOnUIThread() was very long running, it would still block the UI thread for a long time at some point and make the UI unresponsive, is that correct? - Krumelur
  • @Krumelur Yes, it would. Just not immediately - it'd happen at a later time. - Reed Copsey
  • Although this answer is technically correct, the statement that "the rest of the code will execute at a later time" is too abstract and may be misleading. Execution schedule of the code after Task.Yield() is very much dependent on concrete SynchronisationContext. And MSDN documentation clearly states that "The synchronization context that is present on a UI thread in most UI environments will often prioritize work posted to the context higher than input and rendering work. For this reason, do not rely on await Task.Yield(); to keep a UI responsive." - Vitaliy Tsvayer

26

Internally, await Task.Yield() simply queues the continuation on either the current synchronization context or on a random pool thread, if SynchronizationContext.Current is null.

It is efficiently implemented as custom awaiter. A less efficient code producing the identical effect might be as simple as this:

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() can be used as a short-cut for some weird execution flow alterations. For example:

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  
}

That said, I can't think of any case where Task.Yield() cannot be replaced with Task.Factory.StartNew w/ proper task scheduler.

See also:


-3

Task.Yield() may be used in mock implementations of async methods.


Linked


Related

Latest