If I need to postpone code execution until after a future iteration of the UI thread message loop, I could do so something like this:
await Task.Factory.StartNew(
() => {
MessageBox.Show("Hello!");
},
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
This would be similar to await Task.Yield(); MessageBox.Show("Hello!");
, besides I'd have an option to cancel the task if I wanted to.
In case with the default synchronization context, I could similarly use await Task.Run
to continue on a pool thread.
In fact, I like Task.Factory.StartNew
and Task.Run
more than Task.Yield
, because they both explicitly define the scope for the continuation code.
So, in what situations await Task.Yield()
is actually useful?
Consider the case when you want your async task to return a value.
Existing synchronous method:
To make async, add async keyword and change return type:
To use Task.Factory.StartNew(), change the one-line body of the method to:
vs. adding a single line if you use
await Task.Yield()
The latter is far more concise, readable, and really doesn't change the existing method much.
One situation where
Task.Yield()
is actually useful is when you areawait
recursively-called synchronously-completedTask
s. Because csharp’sasync
/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 theTask
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’sTask
before your method performs some action, you can useTask.Yield()
to force returning theTask
earlier than would otherwise naturally happen. For example, in the following local method scenario, theasync
method is able to get a reference to its ownTask
safely (assuming you are running this on a single-concurrencySynchronizationContext
such as in winforms or via nito’sAsyncContext.Run()
):output:
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 ownTask
using aTaskCompletionSource
instead:output:
Task.Yield
isn't an alternative toTask.Factory.StartNew
orTask.Run
. They're totally different. When youawait
Task.Yield
you allow other code on the current thread to execute without blocking the thread. Think of it like awaitingTask.Delay
, exceptTask.Yield
waits until the tasks are complete, rather than waiting for a specific time.Note: Do not use
Task.Yield
on the UI thread and assume the UI will always remain responsive. It's not always the case.Task.Yield()
is great for "punching a hole" in an otherwise synchronous part of anasync
method.Personally I've found it useful in cases where I have a self-cancelling
async
method (one which manages its own correspondingCancellationTokenSource
and cancels the previously created instance on each subsequent call) that can be called multiple times within an extremely short time period (i.e. by interdependent UI elements' event handlers). In such a situation usingTask.Yield()
followed by anIsCancellationRequested
check as soon as theCancellationTokenSource
is swapped out can prevent doing potentially expensive work whose results will end up discarded anyway.Here's an example where only the last queued call to SelfCancellingAsync gets to perform expensive work and run to completion.
The goal here is to allow the code which executes synchronously on the same
SynchronizationContext
immediately after the non-awaited call to the async method returns (when it hits its firstawait
) to change the state that affects the execution of the async method. This is throttling much like that achieved byTask.Delay
(i'm talking about a non-zero delay period here), but without the actual, potentially noticeable delay, which can be unwelcome in some situations.