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 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
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.Yieldin unit tests and to work around an obscure ASP.NET issue where anasyncmethod must not complete synchronously. - Stephen ClearyMessageBox.Show()without passing theIWin32Window ownerargument 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 passIWin32WindowtoMessageBox.Show(), you need to do so on the UI thread. So, in that case, you must not useTask.Run()and must passTaskSchedulertoStartNew(). - 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 thingsasyncmethods just like you normally would even while theMessageBox.Show()is showing. - binki