"await Task.Yield()" and its alternatives
.net, async-await, c#, task-parallel-library
Solution
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
Problem
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?
Related problems
- Suppress warning CS1998: This async method lacks 'await'
- Why is an "await Task.Yield()" required for Thread.CurrentPrincipal to flow correctly?
- Task.Yield - real usages?
- A pattern for self-cancelling and restarting task
- TAP global exception handler
- What is the correct way to use async/await in a recursive method?
- The lack of non-capturing Task.Yield forces me to use Task.Run, why follow that?