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?
One situation where Task.Yield()
is actually useful is when you are await
recursively-called synchronously-completed Task
s. 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
SynchronizationContext
would have to do with it, though. If you are doing something like this, hopefully it's not on the GUI thread if in a graphical application. But if you are recurring with Task APIs, you might not know for certain that the Tasks you're awaiting aren't complete, so it might be necessary to do it. You can always make it conditional on the Task being Task.IsCompleted
and a counter. - binki
Consider the case when you want your async task to return a value.
Existing synchronous method:
public int DoSomething()
{
return SomeMethodThatReturnsAnInt();
}
To make async, add async keyword and change return type:
public async Task<int> DoSomething()
To use Task.Factory.StartNew(), change the one-line body of the method to:
// start new task
var task = Task<int>.Factory.StartNew(
() => {
return SomeMethodThatReturnsAnInt();
},
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext() );
// await task, return control to calling method
await task;
// return task result
return task.Result;
vs. adding a single line if you use await Task.Yield()
// this returns control to the calling method
await Task.Yield();
// otherwise synchronous method scheduled for async execution by the
// TaskScheduler of the calling thread
return SomeMethodThatReturnsAnInt();
The latter is far more concise, readable, and really doesn't change the existing method much.
return await Task.Factory.StartNew(() => SomeMethodThatReturnsAnInt(), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
. Nevertheless, I see the point, +1. The problem with this is somewhat non-intuitive alteration of control flow, discussed here. - noseratioawait task;
line - MohoSomeMethodThatReturnsAnInt
into async
, I could simply do: public Task<int> DoSomething() { return Task.FromResult(SomeMethodThatReturnsAnInt()); }
. Or, for async
semantic of exception propagation: public async Task<int> DoSomething() { return await Task.FromResult(SomeMethodThatReturnsAnInt()); }
. Clearly, await Task.Yield()
would be redundant and undesired here. Sorry for undoing my vote. - noseratio
Task.Yield()
is great for "punching a hole" in an otherwise synchronous part of an async
method.
Personally I've found it useful in cases where I have a self-cancelling async
method (one which manages its own corresponding CancellationTokenSource
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 using Task.Yield()
followed by an IsCancellationRequested
check as soon as the CancellationTokenSource
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.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TaskYieldExample
{
class Program
{
private static CancellationTokenSource CancellationTokenSource;
static void Main(string[] args)
{
SelfCancellingAsync();
SelfCancellingAsync();
SelfCancellingAsync();
Console.ReadLine();
}
private static async void SelfCancellingAsync()
{
Console.WriteLine("SelfCancellingAsync starting.");
var cts = new CancellationTokenSource();
var oldCts = Interlocked.Exchange(ref CancellationTokenSource, cts);
if (oldCts != null)
{
oldCts.Cancel();
}
// Allow quick cancellation.
await Task.Yield();
if (cts.IsCancellationRequested)
{
return;
}
// Do the "meaty" work.
Console.WriteLine("Performing intensive work.");
var answer = await Task
.Delay(TimeSpan.FromSeconds(1))
.ContinueWith(_ => 42, TaskContinuationOptions.ExecuteSynchronously);
if (cts.IsCancellationRequested)
{
return;
}
// Do something with the result.
Console.WriteLine("SelfCancellingAsync completed. Answer: {0}.", answer);
}
}
}
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 first await
) to change the state that affects the execution of the async method. This is throttling much like that achieved by Task.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.
Task.Yield
or helper Task.Delay
, here's my version of cancel-and-restart pattern. - noseratioasync void
signature of SelfCancellingAsync
. - noseratiovoid
, because making it a Task
suggests that it should be awaited, which is not the case here. It's also why I'm returning from the method when a cancellation is detected as opposed to throwing an exception (which would generally be the preferred way of going about communicating cancellation to the caller). - Kirill Shlenskiyasync Task
, throwing exceptions and letting the caller handle them is the preferred way of going about things. In the case of async void
, however, the exception handling generally needs to be wired into the body of the async method itself. - Kirill ShlenskiySelfCancellingAsync
. However, eventually you're going to request a cancellation from outside (e.g., when your Main
method exits and the app terminates). At this point, you'd probably want to wait for the pending task, started by SelfCancellingAsync
, to let it finish gracefully. I can't see how this can be done with your approach. - noseratio
Task.Yield
isn't an alternative to Task.Factory.StartNew
or Task.Run
. They're totally different. When you await
Task.Yield
you allow other code on the current thread to execute without blocking the thread. Think of it like awaiting Task.Delay
, except Task.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
and other custom awaiters work. In this light, I mostly disagree with everything besides your last sentence. - noseratioTask.Yield
waits until the tasks are complete, rather than waiting for a specific time.”—it doesn’t wait for other tasks to complete. It just schedules a “ready-to-run” continuation and switches to the next, existing “ready-to-run” continuation which is waiting in line (which might be itself). I.e., this just forces your task it to stop being synchronous at that point. - binki
Task.Yield
in unit tests and to work around an obscure ASP.NET issue where anasync
method must not complete synchronously. - Stephen ClearyMessageBox.Show()
without passing theIWin32Window owner
argument may result in the messagebox “popping under” your window if that code executes when your window does not have focus. This is particularly confusing if done on the GUI thread. Also, if you do passIWin32Window
toMessageBox.Show()
, you need to do so on the UI thread. So, in that case, you must not useTask.Run()
and must passTaskScheduler
toStartNew()
. - binkiMessageBox.Show()
on a separate thread becauseMessageBox.Show()
pumps the message queue and runs continuations scheduled to the GUI’sSynchronizationContext
. I.e., you can continue updating your form and displaying thingsasync
methods just like you normally would even while theMessageBox.Show()
is showing. - binki