345

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?

Cosmin
  • 2,365
  • 2
  • 23
  • 29
Krumelur
  • 32,180
  • 27
  • 124
  • 263
  • 4
    For any js developers out here, this is equivalent to `setTimeout(_, 0)`. – nawfal Jul 31 '21 at 08:38
  • 1
    I find this blog post really helpful: https://duongnt.com/task-yield/ – Rzassar Aug 14 '22 at 08:58
  • A real world usage is to prevent any synchronized implement of [`BackgroundService.ExecuteAsync()`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.backgroundservice.executeasync) blocking [`IHostedService.StartAsync()`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostedservice.startasync), see https://blog.stephencleary.com/2020/05/backgroundservice-gotcha-startup.html and https://github.com/dotnet/runtime/issues/36063 – n0099 Jun 27 '23 at 03:58

5 Answers5

364

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.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • 41
    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 Mar 25 '14 at 20:17
  • 27
    @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 Mar 25 '14 at 20:18
  • 12
    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 Mar 26 '14 at 08:24
  • 8
    @Krumelur Yes, it would. Just not immediately - it'd happen at a later time. – Reed Copsey Mar 26 '14 at 17:40
  • 57
    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 Dec 23 '15 at 09:05
  • How is this different from `Thread.Yield`? It smells the same to me. – Water Cooler v2 Jun 06 '16 at 23:24
  • It's better to use Dispatcher.BeginInvoke in this case – Yuriy Naydenov Jul 28 '16 at 14:23
  • In your case, `Task.Yield()` did nothing different to `Dispatcher.InvokeAsync` with an empty action. Also, Invoke provide `DispatcherPriority` param to let the UI more responsive than `Yield` which without any params. – walterlv Oct 11 '17 at 04:03
  • 3
    @ReedCopsey you said "@Krumelur Yes, it would. Just not immediately - it'd __happen at a later time__." what you mean by that? I mean who is the boss here? what is the point, it will block the UI anyway, now or later? – Mehdi Dehghani Mar 14 '19 at 13:24
  • 3
    I'm confused... Wouldn't a random `await Task.Delay(1).ConfigureAwait(true);` causes all the stuff below it to run at later time on the UI thread? Is `Task.Yield()` designed for this purpose, or is it just *one possible way to use it*? – Jai Oct 15 '19 at 02:11
  • 1
    Great write up, the only thing that I feel needs to be pointed out *just* to avoid confusion in the method signature; is that it is returning `void`. ***Although*** because you are in an event handler, here it's OK. But generally, When returning from a function / method declared as `async` that doesn't technically return an expected value, it should *almost* always return as type `Task`. Although there are a few exceptions. See section "**Valid Reasons for Returning Void**" here: [Returning Void From a C# Async Method](https://www.pluralsight.com/guides/returning-void-from-c-async-method) – ganjeii Jan 18 '20 at 18:12
  • Here's a linqpad example that demonstrates the utility of `Task.Yield()` : https://gist.github.com/ronnieoverby/c401c97bbf7702088a4e9ccd58f79732 – Ronnie Overby Mar 11 '20 at 12:47
  • 1
    In my case, I *want* the code after `Task.Yield` to be run on the UI thread since it's UI-related. I just don't want it to block when I call a normally user-blocked method (`Window.ShowDialog()` specifically) so I created a `ShowDialogAsync` extension method that returns `Task` and is comprised of two lines... `Task.Yield()` followed by `return window.ShowDialog()`. Does that use-case make sense? Here's the q: https://stackoverflow.com/questions/65453171 – Mark A. Donohoe Dec 26 '20 at 18:19
  • If I understand that, it works a similar way as using BeginInvoke.... within form creation to shift the code within begininvoke to a later time and give the UI time to create the form.... BeginInvoke used in the ui context does nothing else than adding the code to the message queue and continues with everything else until the message queue reaches the point where my code has been added for execution... So yield may be a replacement for that begininvoke "hack" ? – awpross Oct 13 '21 at 13:31
53

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:

noseratio
  • 59,932
  • 34
  • 208
  • 486
  • In your example, what's the difference between what's there and `var dialogTask = await showAsync();`? – Erik Philips Mar 15 '19 at 06:00
  • @ErikPhilips, `var dialogTask = await showAsync()` won't compile because the `await showAsync()` expression doesn't return a `Task` (unlike it does without `await`). That said, if you do `await showAsync()`, the execution after it will be resumed only after the dialog has been closed, that's how it's different. That's because `window.ShowDialog` is a synchronous API (despite it still pumps messages). In that code, I wanted to continue while the dialog is still shown. – noseratio Mar 15 '19 at 07:50
  • Does `await Task.Yield()` have the same effect as `await Task.Delay(1)`? – David Klempfner Nov 11 '21 at 06:39
  • 1
    @DavidKlempfner, yep in this case it should behave the same, but why would you pick `await Task.Delay(1)` over `await Task.Yield()`? – noseratio Nov 11 '21 at 07:00
7

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();
    }
Joakim M. H.
  • 424
  • 4
  • 14
  • 8
    This might prevent a stack overflow, but this will eventually run out of system memory if you let it run long enough. Each iteration would create a new Task that never completes, as the outer Task is awaiting an inner Task, which is awaiting yet another inner Task, and so on. This is not OK. Alternatively, you could simply have one outermost Task that never completes, and just have it loop instead of recurse. The Task would never complete, but there would only be one of them. Inside the loop, it could yield or await anything you'd like. – Triynko Oct 19 '19 at 19:33
  • 4
    I can't reproduce the stack overflow. It seems that the `await Task.Delay(1)` is enough to prevent it. (Console App, .NET Core 3.1, C# 8) – Theodor Zoulias May 19 '20 at 18:45
  • I would have thought `await Task.Delay(1);` and `await Task.Yield();` would do almost exactly the same thing? – David Klempfner Nov 11 '21 at 06:48
  • @DavidKlempfner I haven't worked on .Net for a while, so my information might be a bit outdated, but for .Net 4.7 at least, `await Task.Delay(1)` left it up to the runtime to decide if the delay should happen sequentially without yielding control of the thread or if an unfinished task should be returned and control yielded. On the other hand, `await Task.Yield()` guaranteed that the execution context would yield control of the current thread. – Joakim M. H. Apr 17 '23 at 08:41
5

Task.Yield() is like a counterpart of Thread.Yield() in async-await but with much more specific conditions. How many times do you even need Thread.Yield()? I will answer the title "when would you use Task.Yield()" broadly first. You would when the following conditions are fulfilled:

  • want to return the control to the async context (suggesting the task scheduler to execute other tasks in the queue first)
  • need to continue in the async context
  • prefer to continue immediately when the task scheduler is free
  • do not want to be cancelled
  • prefer shorter code

The term "async context" here means "SynchronizationContext first then TaskScheduler". It was used by Stephen Cleary.

Task.Yield() is approximately doing this (many posts get it slightly wrong here and there):

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

If any one of the conditions is broken, you need to use other alternatives instead.

If the continuation of a task should be in Task.DefaultScheduler, you normally use ConfigureAwait(false). On the contrary, Task.Yield() gives you an awaitable not having ConfigureAwait(bool). You need to use the approximated code with TaskScheduler.Default.

If Task.Yield() obstructs the queue, you need to restructure your code instead as explained by noseratio.

If you need the continuation to happen much later, say, in the order of millisecond, you would use Task.Delay.

If you want the task to be cancellable in the queue but do not want to check the cancellation token nor throw an exception yourself, you need to use the approximated code with a cancellation token.

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

  • a high thread priority for semi-realtime work
  • a low thread priority for some background work
  • a normal thread priority for UI

Thus, I need a custom scheduler. I could easily imagine some poor developers somehow need to mix sync and async code together with some special schedulers in a parallel universe (one universe probably does not contain such developers); but why wouldn't they just use the more robust approximated code so they do not need to write a lengthy comment to explain why and what it does?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
keithyip
  • 985
  • 7
  • 21
  • I don't think that the `Task.Yield` has any affiliation with the `TaskScheduler.Current`. It yields back on the `SynchronizationContext.Current`, not on the current `TaskScheduler`. – Theodor Zoulias Nov 03 '22 at 10:19
  • @TheodorZoulias Are you referring to the approximated code? I am not sure what "`Task.Yield` has any affiliation with the `TaskScheduler.Current`" means. `Task.Yield()` prefers to continue on `SynchronizationContext.Current`. If `SynchronizationContext.Current` is null, the continuation will be enqueued to the original queue. I am not aware of a way to do the same thing as `Task.Yield()`. The closest way, although still different, is the code above. This is one of the reasons why there exists `Task.Yield()`. – keithyip Nov 05 '22 at 11:44
  • Yes, I am referring to the approximated code, that schedules the continuation on the `TaskScheduler.Current` when the `SynchronizationContext.Current` is null. – Theodor Zoulias Nov 05 '22 at 11:57
  • I just checked the [source code](https://github.com/dotnet/runtime/blob/b59c6491dd9636d436ad10e087523a163fb93be0/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/YieldAwaitable.cs#L95) of the `YieldAwaitable` and I was wrong. The `Task.Yield` does indeed capture to the `TaskScheduler.Current` when there is no `SynchronizationContext.Current` to capture. So your approximation of `Task.Yield` is correct. – Theodor Zoulias Nov 05 '22 at 12:08
-1

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

Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
mhsirig
  • 95
  • 5
    You should provide some details. – PJProudhon Mar 11 '18 at 09:36
  • 5
    For this purpose, I'd rather use [Task.CompletedTask](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.completedtask(v=vs.110).aspx) - see section Task.CompletedTask in [this msdn blog post](https://blogs.msdn.microsoft.com/pfxteam/2015/02/02/new-task-apis-in-net-4-6/) for more considerations. – Grzegorz Smulko May 12 '18 at 21:35
  • 7
    The problem with using Task.CompletedTask, or Task.FromResult is that you could miss bugs that only appear when the method executes asynchronous. – Joakim M. H. Sep 02 '19 at 08:20