When Would I Use Task.Yield()

When would I use Task.Yield()?

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.

Task.Yield - real usages?

When you see:

await Task.Yield();

you can think about it this way:

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

All this does is makes sure the continuation will happen asynchronously in the future. By asynchronously I mean that the execution control will return to the caller of the async method, and the continuation callback will not happen on the same stack frame.

When exactly and on what thread it will happen completely depends on the caller thread's synchronization context.

For a UI thread, the continuation will happen upon some future iteration of the message loop, run by Application.Run (WinForms) or Dispatcher.Run (WPF). Internally, it comes down to the Win32 PostMessage API, which post a custom message to the UI thread's message queue. The await continuation callback will be called when this message gets pumped and processed. You're completely out of control about when exactly this is going to happen.

Besides, Windows has its own priorities for pumping messages: INFO: Window Message Priorities. The most relevant part:

Under this scheme, prioritization can be considered tri-level. All
posted messages are higher priority than user input messages because
they reside in different queues. And all user input messages are
higher priority than WM_PAINT and WM_TIMER messages.

So, if you use await Task.Yield() to yield to the message loop in attempt to keep the UI responsive, you are actually at risk of obstructing the UI thread's message loop. Some pending user input messages, as well as WM_PAINT and WM_TIMER, have a lower priority than the posted continuation message. Thus, if you do await Task.Yield() on a tight loop, you still may block the UI.

This is how it is different from the JavaScript's setTimer analogy you mentioned in the question. A setTimer callback will be called after all user input message have been processed by the browser's message pump.

So, await Task.Yield() is not good for doing background work on the UI thread. In fact, you very rarely need to run a background process on the UI thread, but sometimes you do, e.g. editor syntax highlighting, spell checking etc. In this case, use the framework's idle infrastructure.

E.g., with WPF you could do await Dispatcher.Yield(DispatcherPriority.ApplicationIdle):

async Task DoUIThreadWorkAsync(CancellationToken token)
{
var i = 0;

while (true)
{
token.ThrowIfCancellationRequested();

await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);

// do the UI-related work item
this.TextBlock.Text = "iteration " + i++;
}
}

For WinForms, you could use Application.Idle event:

// await IdleYield();

public static Task IdleYield()
{
var idleTcs = new TaskCompletionSource<bool>();
// subscribe to Application.Idle
EventHandler handler = null;
handler = (s, e) =>
{
Application.Idle -= handler;
idleTcs.SetResult(true);
};
Application.Idle += handler;
return idleTcs.Task;
}

It is recommended that you do not exceed 50ms for each iteration of such background operation running on the UI thread.

For a non-UI thread with no synchronization context, await Task.Yield() just switches the continuation to a random pool thread. There is no guarantee it is going to be a different thread from the current thread, it's only guaranteed to be an asynchronous continuation. If ThreadPool is starving, it may schedule the continuation onto the same thread.

In ASP.NET, doing await Task.Yield() doesn't make sense at all, except for the workaround mentioned in @StephenCleary's answer. Otherwise, it will only hurt the web app performance with a redundant thread switch.

So, is await Task.Yield() useful? IMO, not much. It can be used as a shortcut to run the continuation via SynchronizationContext.Post or ThreadPool.QueueUserWorkItem, if you really need to impose asynchrony upon a part of your method.

Regarding the books you quoted, in my opinion those approaches to using Task.Yield are wrong. I explained why they're wrong for a UI thread, above. For a non-UI pool thread, there's simply no "other tasks in the thread to execute", unless you running a custom task pump like Stephen Toub's AsyncPump.

Updated to answer the comment:

... how can it be asynchronouse operation and stay in the same thread
?..

As a simple example: WinForms app:

async void Form_Load(object s, object e) 
{
await Task.Yield();
MessageBox.Show("Async message!");
}

Form_Load will return to the caller (the WinFroms framework code which has fired Load event), and then the message box will be shown asynchronously, upon some future iteration of the message loop run by Application.Run(). The continuation callback is queued with WinFormsSynchronizationContext.Post, which internally posts a private Windows message to the UI thread's message loop. The callback will be executed when this message gets pumped, still on the same thread.

In a console app, you can run a similar serializing loop with AsyncPump mentioned above.

Explain await Task.Yield in an ASPNET Core WebApi context

I'm expecting "End DoSomethingAsync" to get logged first and "DoSomethingAsync: Completed" to come a second later

That is an incorrect expectation; the await here:

_logger.LogInformation("GET: Start DoSomethingAsync");    
await DoSomethingAsync();
_logger.LogInformation("GET: End DoSomethingAsync");

means that the 3rd line doesn't happen until everything in DoSomethingAsync() has completed asynchronously if needed. You can get the behaviour you want via:

_logger.LogInformation("GET: Start DoSomethingAsync");    
var pending = DoSomethingAsync();
_logger.LogInformation("GET: End DoSomethingAsync");
await pending;

however, this is fundamentally the same as adding a second thread in regular non-async code - you now have two execution flows active on the same request. This can be valid, but can also be dangerous if thread-safety semantics are not observed.

await Task.Yield() and its alternatives

One situation where Task.Yield() is actually useful is when you are await recursively-called synchronously-completed Tasks. Because csharp’s async/await “releases Zalgo” by running continuations synchronously when it can, the stack in a fully synchronous recursion scenario can get big enough that your process dies. I think this is also partly due to tail-calls not being able to be supported because of the Task indirection. await Task.Yield() schedules the continuation to be run by the scheduler rather than inline, allowing growth in the stack to be avoided and this issue to be worked around.

Also, Task.Yield() can be used to cut short the synchronous portion of a method. If the caller needs to receive your method’s Task before your method performs some action, you can use Task.Yield() to force returning the Task earlier than would otherwise naturally happen. For example, in the following local method scenario, the async method is able to get a reference to its own Task safely (assuming you are running this on a single-concurrency SynchronizationContext such as in winforms or via nito’s AsyncContext.Run()):

using Nito.AsyncEx;
using System;
using System.Threading.Tasks;

class Program
{
// Use a single-threaded SynchronizationContext similar to winforms/WPF
static void Main(string[] args) => AsyncContext.Run(() => RunAsync());

static async Task RunAsync()
{
Task<Task> task = null;
task = getOwnTaskAsync();
var foundTask = await task;
Console.WriteLine($"{task?.Id} == {foundTask?.Id}: {task == foundTask}");

async Task<Task> getOwnTaskAsync()
{
// Cause this method to return and let the 「task」 local be assigned.
await Task.Yield();
return task;
}
}
}

output:

3 == 3: True

I am sorry that I cannot think up any real-life scenarios where being able to forcibly cut short the synchronous portion of an async method is the best way to do something. Knowing that you can do a trick like I just showed can be useful sometimes, but it tends to be more dangerous too. Often you can pass around data in a better, more readable, and more threadsafe way. For example, you can pass the local method a reference to its own Task using a TaskCompletionSource instead:

using System;
using System.Threading.Tasks;

class Program
{
// Fully free-threaded! Works in more environments!
static void Main(string[] args) => RunAsync().Wait();

static async Task RunAsync()
{
var ownTaskSource = new TaskCompletionSource<Task>();
var task = getOwnTaskAsync(ownTaskSource.Task);
ownTaskSource.SetResult(task);
var foundTask = await task;
Console.WriteLine($"{task?.Id} == {foundTask?.Id}: {task == foundTask}");

async Task<Task> getOwnTaskAsync(
Task<Task> ownTaskTask)
{
// This might be clearer.
return await ownTaskTask;
}
}
}

output:

2 == 2: True

What's the difference between Task.Yield, Task.Run, and ConfigureAwait(false)?

Task.Yield is not a replacement for Task.Run and it has nothing to do with Task.ConfigureAwait.

  • Task.Yield - Generates an awaitable that completes just after it is checked for completion.
  • ConfigureAwait(false) - Generates an awaitable from a task that disregards the captured SynchronizationContext.
  • Task.Run - Executes a delegate on a ThreadPool thread.

Task.Yield is different than ConfigureAwait in that it is an awaitable all by itself, and not a configurable wrapper of another awaitable (i.e. the Task). Another difference is that Task.Yield does continue on the captured context.

Task.Run is different then both as it just takes a delegate and runs it on the ThreadPool, you can use it with ConfigureAwait(false) or without.

Task.Yield should be used to force an asynchronous point, not as a replacement to Task.Run. When await is reached in an async method it checks whether the task (or other awaitable) already completed and if so it continues on synchronously. Task.Yield prevents that from happening so it's useful for testing.

Another usage is in UI methods, where you don't want to be hogging the single UI thread, you insert an asynchronous point and the rest is scheduled to be executed in a later time.

Task.Yield() versus Task.Delay(0)

Inside Task.Delay, it looks like this (the single parameter (int) version just calls the below version):

[__DynamicallyInvokable]
public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken)
{
if (millisecondsDelay < -1)
{
throw new ArgumentOutOfRangeException("millisecondsDelay", Environment.GetResourceString("Task_Delay_InvalidMillisecondsDelay"));
}
if (cancellationToken.IsCancellationRequested)
{
return FromCancellation(cancellationToken);
}
if (millisecondsDelay == 0)
{
return CompletedTask;
}
DelayPromise state = new DelayPromise(cancellationToken);
if (cancellationToken.CanBeCanceled)
{
state.Registration = cancellationToken.InternalRegisterWithoutEC(delegate (object state) {
((DelayPromise) state).Complete();
}, state);
}
if (millisecondsDelay != -1)
{
state.Timer = new Timer(delegate (object state) {
((DelayPromise) state).Complete();
}, state, millisecondsDelay, -1);
state.Timer.KeepRootedWhileScheduled();
}
return state;
}

As you can hopefully see:

    if (millisecondsDelay == 0)
{
return CompletedTask;
}

Which means it always returns a completed task, and therefore your code will always continue running past that particular await line.

Task.Yield(); SyncAction(); vs Task.Run(()=SyncAction());

While both options are bad, there is a difference (important in GUI applications): after Task.Yield returns, the rest of the method will be dispatched back to original SynchronizationContext. If you ran that from UI thread, your long running operation will be executed on UI thread, thus freezing it. So if your intention was to avoid UI blocking - Task.Yield won't work. With Task.Run that won't happen.

Is calling Task.Yield in Asp.Net Core ensuring continuation to run on the ThreadPool?

Assuming an Asp.Net Core application which does not have a SynchronizationContext, is there any functional difference from awaiting Task.Yield and passing that continuation to Task.Run?

There's practically no difference. await Task.Yield will queue the continuation to the thread pool and return to the caller. await Task.Run will queue its delegate to the thread pool and return to the caller.

Personally, I prefer Task.Run because it has a very explicit intent and because it's unambiguous (i.e., in other scenarios such as GUI threads, await Task.Yield will not queue to the thread pool, while await Task.Run always does the same thing regardless of context).

As others have noted in the comments, there's no reason to actually do either one on ASP.NET Core, which is already running on thread pool threads and does not have a request context. It would add a little overhead to the request while providing no benefit at all.

When is it asynchronous and when is synchronous

This answer contains general info and advices, and it's not focused on the code that you posted.

  1. Being confused while learning async-await is OK. Getting a perfect understanding of async-await without going through a confusion phase is practically almost impossible IMHO.
  2. The await Task.Yield(), await Task.Delay(1), await Task.Run(() => { }), and .ConfigureAwait(false) are dirty hacks that people sometimes use out of frustration that an explicit way to switch imperatively to the ThreadPool context does not exist (in the standard libraries). A SwitchTo method existed in some pre-prelease .NET version, then it was removed for technical reasons, then the technical reasons were eliminated, reintroducing the method never became a high enough priority, and so it didn't happen. It may happen in the future, but don't hold your breath. It is currently available in the Microsoft.VisualStudio.Threading package, if you want it.
  3. You don't need any of these hacks to write async-await code successfully. If you want to offload work to the ThreadPool, there is the Task.Run method that does the job perfectly.
  4. Offloading work to the ThreadPool should be done at the "application code" level, not at the "library code" level. You can read this article to understand why exposing asynchronous wrappers for synchronous methods is not a good idea.

Difference between using Task.Yield() in a User Interface and Console App

Watching what happens with the debugger, the process gets to await
Task.Yield() and never progresses to return form.ShowDialog() and thus
you never see the RunningForm. The process then goes to
LoadDataAsync() and hangs forever on await Task.Delay(2000).

Why is this happening?

What happens here is that when you do var runningForm = new RunningForm() on a console thread without any synchronization context (System.Threading.SynchronizationContext.Current is null), it implicitly creates an instance of WindowsFormsSynchronizationContext and installs it on the current thread, more on this here.

Then, when you hit await Task.Yield(), the ShowDialogAsync method returns to the caller and the await continuation is posted to that new synchronization context. However, the continuation never gets a chance to be invoked, because the current thread doesn't run a message loop and the posted messages don't get pumped. There isn't a deadlock, but the code after await Task.Yield() is never executed, so the dialog doesn't even get shown. The same is true about await Task.Delay(2000).

I'm more interested in learning why it works for WinForms and not for
Console Applications.

You need a UI thread with a message loop in your console app. Try refactoring your console app like this:

public void Run()
{
var runningForm = new RunningForm();
runningForm.Loaded += async delegate
{
runningForm.ShowRunning();

var progressFormTask = runningForm.ShowDialogAsync();

var data = await LoadDataAsync();

runningForm.Close();

await progressFormTask;

MessageBox.Show(data.ToString());
};
System.Windows.Forms.Application.Run(runningForm);
}

Here, the job of Application.Run is to start a modal message loop (and install WindowsFormsSynchronizationContext on the current thread) then show the form. The runningForm.Loaded async event handler is invoked on that synchronization context, so the logic inside it should work just as expected.

That however makes Test.Run a synchronous method, i. e., it only returns when the form is closed and the message loop has ended. If this is not what you want, you'd have to create a separate thread to run your message loop, something like I do with MessageLoopApartment here.

That said, in a typical WinForms or WPF application you should almost never need a secondary UI thread.



Related Topics



Leave a reply



Submit