ConfigureAwaitOptions
in .NET 8
Intro
In .NET, we can set ConfigureAwait(false)
for Task operations to avoid returning to the original synchronization context after the asynchronous operation is completed. .NET 8 introduced a ConfigureAwaitOptions
The usage of ConfigureAwait
has been further expanded. Let’s take a look at how to use it.
Definition
ConfigureAwaitOptions
is a flag enumeration, defined as follows:
namespace System.Threading.Tasks; /// <summary>Options to control behavior when awaiting.</summary> [Flags] public enum ConfigureAwaitOptions { /// <summary>No options specified.</summary> /// <remarks> /// <see cref="Task.ConfigureAwait(ConfigureAwaitOptions)"/> with a <see cref="None"/> argument behaves /// identically to using <see cref="Task.ConfigureAwait(bool)"/> with a <see langword="false"/> argument. /// </remarks> None = 0x0, /// <summary> /// Attempt to marshal the continuation back to the original <see cref="SynchronizationContext"/> or /// <see cref="TaskScheduler"/> present on the originating thread at the time of the await. /// </summary> /// <remarks> /// If there is no such context/scheduler, or if this option is not specified, the thread on /// which the continuation is invoked is unspecified and left up to the determination of the system. /// <see cref="Task.ConfigureAwait(ConfigureAwaitOptions)"/> with a <see cref="ContinueOnCapturedContext"/> argument /// behaves identically to using <see cref="Task.ConfigureAwait(bool)"/> with a <see langword="true"/> argument. /// </remarks> ContinueOnCapturedContext = 0x1, /// <summary> /// Avoids throwing an exception at the completion of awaiting a <see cref="Task"/> that ends /// in the <see cref="TaskStatus.Faulted"/> or <see cref="TaskStatus.Canceled"/> state. /// </summary> /// <remarks> /// This option is supported only for <see cref="Task.ConfigureAwait(ConfigureAwaitOptions)"/>, /// not <see cref="Task{TResult}.ConfigureAwait(ConfigureAwaitOptions)"/>, as for a <see cref="Task{TResult}"/> the /// operation could end up returning an incorrect and/or invalid result. To use with a <see cref="Task{TResult}"/>, /// cast to the base <see cref="Task"/> type in order to use its <see cref="Task.ConfigureAwait(ConfigureAwaitOptions)"/>. /// </remarks> SuppressThrowing = 0x2, /// <summary> /// Forces an await on an already completed <see cref="Task"/> to behave as if the <see cref="Task"/> /// wasn't yet completed, such that the current asynchronous method will be forced to yield its execution. /// </summary> ForceYielding = 0x4, }
-
None
is equivalent toConfigureAwait(false)
-
ContinueOnCapturedContext
is equivalent toConfigureAwait(true)
-
SuppressThrowing
will not throw an exception in the case of task cancel or exception. This is only valid forTask
, forTask
that has a return value. code> is invalid -
ForceYielding
forcesyeild
even if the task has been completed
Sample
ForceYield
sample
public static async Task ForceYielding() { DumpThreadInfo(); await Task.CompletedTask; DumpThreadInfo(); await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); DumpThreadInfo(); } private static void DumpThreadInfo() { Console.WriteLine($"ThreadId: {Environment.CurrentManagedThreadId}"); }
The output is as follows:
You can see that when ConfigureAwait
is not used, the actual thread does not change, but after adding .ConfigureAwait(ConfigureAwaitOptions.ForceYielding)
, the subsequent threads have actually changed.
For the completed Task
, the asynchronous state machine will not be generated by default. The two will actually be executed on the same thread. .ConfigureAwait(ConfigureAwaitOptions.ForceYielding)
An asynchronous state machine will still be generated, not necessarily the same thread before and after
This can be considered using Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding)
when we want to use Task.Yield
Task.Yield
is equivalent to using Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext | ConfigureAwaitOptions.ForceYielding)
SuppressThrowing
sample
Sometimes we will await a task, but an error in the execution of this task will not affect the main process, similar to the following code
try { await task().ConfigureAwait(false); } catch { // ignore }
For such code, we can use the SuppressThrowing
option to simplify the code, which can be directly transformed into await task().ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing)
, without the need for us to display it again. Try...catch style
Let’s see an example:
using var cts = new CancellationTokenSource(); cts.CancelAfter(100); try { await Task.Delay(1000, cts.Token); } catch (Exception e) { Console.WriteLine(e); } try { await Task.Delay(1000, cts.Token); } catch (Exception e) { Console.WriteLine(e); } var startTimestamp = TimeProvider.System.GetTimestamp(); await Task.Delay(1000, cts.Token).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); Console.WriteLine($"{TimeProvider.System.GetElapsedTime(startTimestamp).TotalMicroseconds} ms"); try { await ThrowingTask(); } catch (Exception e) { Console.WriteLine(e); } await ThrowingTask().ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); Console.WriteLine("Yeah");
What will be the output? The output is as follows:
You can see that after adding .ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing)
when task cancel and task exception occur, the exception will be swallowed and will not affect the execution of the main process.
We mentioned earlier that Task
with a return value will not take effect. If it is used against Task
, there will be a warning.
try { // CA2261: The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task await ThrowingTaskWithReturnValue().ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); } catch (Exception e) { Console.WriteLine(e); } private static async Task<int> ThrowingTaskWithReturnValue() { await Task.Delay(100); throw new InvalidOperationException("Balabala2"); }
The output is as follows:
When actually used, an error will be reported at runtime.
More
Using the new ConfigureAwaitOptions
can simplify some existing code. You can refer to the PR that introduced this feature https://github.com/dotnet/runtime/pull/87067
And a PR to improve hosting based on this https://github.com/dotnet/runtime/pull/93949
References
-
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.configureawaitoptions?view=net-8.0
-
https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ConfigureAwaitOptions.cs
-
https://github.com/dotnet/runtime/pull/87067
-
https://github.com/dotnet/runtime/pull/87067/files#diff-55c104dbddd158b7d649d07c342facc10f353b1b036bb35a0ad71625073eb637
-
https://github.com/dotnet/runtime/pull/93949
-
https://github.com/WeihanLi/SamplesInPractice/blob/master/net8sample/Net8Sample/ConfigureAwaitOptionsSample.cs
The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Algorithm skill tree Home page Overview 57287 people are learning the system