Krumelur
Krumelur

Reputation: 33068

When would I use Task.Yield()?

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?

Upvotes: 383

Views: 135038

Answers (8)

Tore Aurstad
Tore Aurstad

Reputation: 3836

In Asp.net Core, having multiple BackgroundService implementations (classes inheriting from BackgroundService in Microsoft.Extensions.Hosting.Abstractions) starting up and one of them for example is running synchronously and slow, you can bypass the wait time for the other BackgroundService implementations using Task.Yield()

E.g. ScoreProcessingService.cs :

protected async Task ExecuteAsync(CancellationToken stoppingToken){
  //await Task.Yield();
  //simulate a slow synchronous operation : 
  Thread.Sleep(5000);
 //some code
}

QueryServiceProcessingService.cs :

protected async Task ExecuteAsync(CancellationToken stoppingToken){
 //some code
}

Using await Task.Yield() in ScoreProcessingService, we can start up other background services immediately and do not have to wait for the background service to start up.

This will in effect give you quicker startup time for an Asp.net Core application, since all background services must be started in the initialization of the application.

Upvotes: -2

Theodor Zoulias
Theodor Zoulias

Reputation: 43941

I 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.

You have never used the Task.Yield, because this API has no clear purpose in life. Apart from Noseratio's rather esoteric (yet clever and sophisticated) use for juggling between nested UI message loops, I have yet to see someone using this API for a purpose that could be described as "intended", and also a purpose that cannot be fulfilled in a better way using other APIs with clear-cut semantics.

Most people discover the Task.Yield when they want to offload work to the ThreadPool, and then use it because it works. There are two problems with this:

  1. There is a Task.Run API that exists for exactly this reason. Offloading work to the ThreadPool is its documented purpose.
  2. The await Task.Yield() yields to the ThreadPool only in the absence of a synchronization context, in other words only when the SynchronizationContext.Current is null. Otherwise it yields to wherever the current synchronization context thinks it should yield. Since the Task.Yield is documented to "asynchronously yield back to the current context when awaited", it could be said that any behavior it has in the absence of a synchronization context is accidental and not to be relied on.

I'll finish this answer with an example showing how to modify existing solutions that use incorrectly the await Task.Yield() for the purpose of offloading, by replacing it with the correct API Task.Run.

Incorrect:

foreach (var file in files)
{
    await Task.Yield();
    Process(file);
}

Fix #1 (preserving the above behavior that schedules on the ThreadPool the processing of each file individually):

foreach (var file in files) 
{
    await Task.Run(() => Process(file));
}

Fix #2 (doing all the work on a single ThreadPool thread without any application-level interruption):

await Task.Run(() =>
{
    foreach (var file in files) 
    {
        Process(file);
    }
});

If you think that the await Task.Yield(); is sexier than the await Task.Run(() => { });, you might be looking for the SwitchTo API that currently exists in the Microsoft.VisualStudio.Threading package. To learn why this API doesn't exist in the standard .NET libraries, see this question.

Upvotes: 6

Gonan
Gonan

Reputation: 773

One useful case I found for Yield is when creating a method (specially in tests) that returns an IAsyncEnumerable type.

private static async IAsyncEnumerable<Response> CreateAsyncEnumerable() {
        await Task.Yield();
        yield return new Response ("data");
    }

In this case, if I don't add the await Task.Yield();, it still works but it complains with:

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Other examples often simulate async by adding Task.Delay(), but I'd rather not in tests.

Upvotes: -2

keithyip
keithyip

Reputation: 1035

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?

Upvotes: 8

Joakim M. H.
Joakim M. H.

Reputation: 469

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

Upvotes: 7

noseratio
noseratio

Reputation: 61744

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:

Upvotes: 60

mhsirig
mhsirig

Reputation: 91

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

Upvotes: -3

Reed Copsey
Reed Copsey

Reputation: 564861

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.

Upvotes: 400

Related Questions