When would I use Task.Yield()?

前端 未结 4 1653
走了就别回头了
走了就别回头了 2020-11-29 15:09

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 wh

相关标签:
4条回答
  • 2020-11-29 15:23

    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.

    0 讨论(0)
  • 2020-11-29 15:24

    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:

    • "await Task.Yield()" and its alternatives

    • Task.Yield - real usages?

    0 讨论(0)
  • 2020-11-29 15:25

    One use of Task.Yield() is to prevent a stack overflow when doing async recursion. Task.Yield() prevents syncronous continuation. Note, however, that this can cause an OutOfMemory exception (as noted by Triynko). Endless recursion is still not safe and you're probably better off rewriting the recursion as a loop.

    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();
        }
    
    0 讨论(0)
  • 2020-11-29 15:36

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

    0 讨论(0)
提交回复
热议问题