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?
You can use await Task. Yield(); in an asynchronous method to force the method to complete asynchronously. If there is a current synchronization context (SynchronizationContext object), this will post the remainder of the method's execution back to that context.
If you don't await the task or explicitly check for exceptions, the exception is lost. If you await the task, its exception is rethrown. As a best practice, you should always await the call. By default, this message is a warning.
If you use Task. Run with an I/O operation, you're creating a thread (and probably occupying a CPU core) that will mostly be waiting. It may be a quick and easy way to keep your application responsive, but it's not the most efficient use of system resources. A much better approach is to use await without Task.
Async methods are intended to be non-blocking operations. An await expression in an async method doesn't block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method.
The await Task.Yield() construct does a fairly simple thing – it interrupts the current method and immediately schedules its continuation in the current synchronization context. This design is used for different purposes. First , this construct can be used to immediately return control to the calling code.
Instead, call Task.Yield () to yield control to another task. await Task.Delay (...) is a drop-in replacement for Thread.Sleep () within an async method.
The first one never seems to end. await asynchronously unwraps the result of your task, whereas just using Result would block until the task had completed. See this explanantion from Jon Skeet.
In this article we will see why “ await Task” is almost always the right choice, even if highly disruptive. Thread pool contention is a well-known challenge to seasoned developers. Services should process work at the same rate as they receive it. If they aren’t then they drag a bunch of additional work that slows down the system:
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With