26

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?

noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 4
    I've only used `Task.Yield` in unit tests and [to work around an obscure ASP.NET issue where an `async` method *must not* complete synchronously](http://stackoverflow.com/q/16653308/263693). – Stephen Cleary Dec 02 '13 at 13:18
  • 2
    Related: [Task.Yield - real usages?](http://stackoverflow.com/q/23431595/1768303) – noseratio Feb 02 '15 at 02:22
  • 1
    I understand that your question didn’t ask this, but it is kind of related. Calling `MessageBox.Show()` without passing the [`IWin32Window owner` argument](https://msdn.microsoft.com/en-us/library/cked7698%28v=vs.110%29.aspx) 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 pass `IWin32Window` to `MessageBox.Show()`, you need to do so on the UI thread. So, in that case, you **must not** use `Task.Run()` and **must** pass `TaskScheduler` to `StartNew()`. – binki Oct 13 '17 at 03:36
  • Additionally, there’s no need to put `MessageBox.Show()` on a separate thread because `MessageBox.Show()` pumps the message queue and runs continuations scheduled to the GUI’s `SynchronizationContext`. I.e., you can continue updating your form and displaying things `async` methods just like you normally would even while the `MessageBox.Show()` is showing. – binki Mar 21 '18 at 16:17

4 Answers4

8

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.

Noctis
  • 11,507
  • 3
  • 43
  • 82
Kirill Shlenskiy
  • 9,367
  • 27
  • 39
  • Thanks for your thoughts, although I'm doing the same without `Task.Yield` or helper `Task.Delay`, [here's my version](http://stackoverflow.com/a/20320896/1768303) of cancel-and-restart pattern. – noseratio Dec 02 '13 at 04:35
  • Btw, you may have issues with handling exceptions from your "meaty" work, because of `async void` signature of `SelfCancellingAsync`. – noseratio Dec 02 '13 at 04:38
  • 1
    I am specifically *not* handling any exceptions for the sake of brevity - that is the job of whoever chooses to use the pattern. The example had to be a `void`, 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 Shlenskiy Dec 02 '13 at 04:44
  • AFAIU, it won't be possible for a user of the pattern to handle any exceptions, this won't work: `try { SelfCancellingAsync() } catch (Exception e) { /* never here */ }`, and so won't this: `try { await SelfCancellingAsync() } catch (Exception e) { /* never here */ }`. – noseratio Dec 02 '13 at 04:57
  • 1
    That is correct, and it's a key difference between the two types of async methods. Whenever you have an `async 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 Shlenskiy Dec 02 '13 at 05:08
  • 1
    I guess it may be OK if your task is totally isolated and you handle *all* exceptions inside `SelfCancellingAsync`. 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 Dec 02 '13 at 06:27
  • I'd rather make `SelfCancellingAsync` a synchronous method, remove `await Task.Yield()`, run the rest of the method after `await Task.Yield()` as `Task.Run` (without `await`) and keep the reference to the returned task. Then I could request cancellation and do something like `task.Wait()` when the app terminates. – noseratio Dec 02 '13 at 06:28
7

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.

Moho
  • 15,457
  • 1
  • 30
  • 31
  • 2
    To be fair, it's still a single line: `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](http://stackoverflow.com/q/18779393/1768303). – noseratio Dec 02 '13 at 04:48
  • well, you also need the `await task;` line – Moho Dec 02 '13 at 04:49
  • 2
    Revisiting this, if I need to turn `SomeMethodThatReturnsAnInt` into `async`, I could simply do: `public Task DoSomething() { return Task.FromResult(SomeMethodThatReturnsAnInt()); }`. Or, for [`async` semantic of exception propagation](http://stackoverflow.com/a/22395161/1768303): `public async Task DoSomething() { return await Task.FromResult(SomeMethodThatReturnsAnInt()); }`. Clearly, `await Task.Yield()` would be redundant and undesired here. Sorry for undoing my vote. – noseratio Apr 20 '14 at 01:57
  • It’s much better to [simply suppress the CS1998 warning](https://stackoverflow.com/a/16067003/429091) than force a context switch just to indirectly suppress the compiler warning. – binki Oct 13 '17 at 02:01
  • 1
    @noseratio `Task.FromResult(SomeMethodThatReturnsAnInt());` still executes synchronously. `FromResult` and counterparts returns a completed task, hence no context suspension happens. `Yield` on the other hand suspends the current method until event loop is free and resumes from there. Sometimes latter behaviour is required. – nawfal Oct 10 '21 at 11:02
5

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
binki
  • 7,754
  • 5
  • 64
  • 110
  • Tackling recursion is indeed a good use for it, although the cost of it highly depends on a particular synchronization context. – noseratio Oct 14 '17 at 03:28
  • @noseratio True. The fact that it has overhead can be mitigated a bit by passing a counter around and only using it every 30 recursions or so. I'm not so sure what a `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 Oct 14 '17 at 12:40
-1

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.

Torra
  • 1,182
  • 5
  • 15
  • 23
  • 9
    I believe I have a good understanding of how `Task.Yield` and other custom awaiters work. In this light, **I mostly disagree with everything besides your last sentence.** – noseratio Dec 02 '13 at 02:47
  • 3
    “except `Task.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 Oct 13 '17 at 02:04