1024programmer Asp.Net ConfigureAwait in .NET8

ConfigureAwait in .NET8

ConfigureAwait in .NET8


2023-11-24 10:14
Sleeping Mu Mu Xi
Read(1392)
Comment(4)
edit
collect
report

ConfigureAwait in .NET8

ConfigureAwait(true) and ConfigureAwait(false)

First, let’s review the semantics and history of the original ConfigureAwait, which took a boolean parameter called continueOnCapturedContext.

When executing on a task (Task, Task, ValueTask or ValueTask) The default behavior of an await operation is to capture the “context”; later, when the task is completed, the async method will continue execution in that context. The “context” is SynchronizationContext.Current or TaskScheduler.Current (if no context is provided, falls back to the thread pool context). This default behavior of continuing in a captured context can be made explicit by using ConfigureAwait(continueOnCapturedContext: true).

ConfigureAwait(continueOnCapturedContext: false) is useful if you do not want to resume on that context. When using ConfigureAwait(false), the asynchronous method resumes on any available thread pool thread.

ConfigureAwait(false) has an interesting history (at least to me). Initially, the community recommended using ConfigureAwait(false) wherever possible, unless context is required. This is also the stance I recommend in my Async Best Practices article. During that time we had many discussions about the reasons for defaulting to true, especially among library developers who had to use ConfigureAwait(false) frequently.

However, the recommendation to “use ConfigureAwait(false) whenever possible” has changed over the years. The first (albeit minor) change is that instead of “use ConfigureAwait(false) whenever possible”, there is a simpler guideline: use in library code ConfigureAwait(false) instead of using it in application code. This guideline is easier to understand and follow. Still, complaints about having to use ConfigureAwait(false) continue, and from time to time there are requests to change the default project-wide. The C# team always denies these requests due to language consistency concerns.

Recently (specifically, since ASP.NET abandoned SynchronizationContext in ASP.NET Core and fixed all the places that needed sync-over-async) , the C# team began to abandon the use of ConfigureAwait(false). As a library author, I completely understand how annoying it is to have ConfigureAwait(false) everywhere in your code base! Some library authors have decided not to use ConfigureAwait(false) anymore. Personally, I still use ConfigureAwait(false) in my libraries, but I understand the frustration.

Now that we’re talking about ConfigureAwait(false), I’d like to point out a few common misconceptions:

  1. ConfigureAwait(false) is not a good way to avoid deadlock. This is not its purpose and is a questionable solution at best. To avoid deadlocks when blocking directly, you must ensure that all asynchronous code uses ConfigureAwait(false), including code in libraries and runtimes. This is not a very easy to maintain solution. There are better solutions.
  2. ConfigureAwait configures await, not the task. For example, ConfigureAwait(false) in SomethingAsync().ConfigureAwait(false).GetAwaiter().GetResult() has absolutely no effect. Likewise, await in var task = SomethingAsync(); task.ConfigureAwait(false); await task; still continues in the captured context, completely ignoring ConfigureAwait(false). I’ve seen both of these mistakes over the years.
  3. ConfigureAwait(false) does not mean “run the subsequent part of this method on the thread pool thread” or “run the subsequent part of this method on a different thread”. It only takes effect if await pauses execution and later resumes the asynchronous method. Specifically, await will not pause execution if its task has already completed; in this case, ConfigureAwait will not work because await will continue execution synchronously.

Okay, now that we have re-understood ConfigureAwait(false), let’s take a look at how ConfigureAwait has been enhanced in .NET8. ConfigureAwait(true) and ConfigureAThe assumption of � is that you intentionally dropped the exception while waiting for the task, so it won't be considered unobserved.

TaskScheduler.UnobservedTaskException += (_, __) => { Console.WriteLine("never printed"); };

 Task task = Task.FromException(new InvalidOperationException());
 await task.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
 task = null;

 GC.Collect();
 GC.WaitForPendingFinalizers();
 GC.Collect();

 Console.ReadKey();
 

There is another consideration for this markup. When used with Task, the semantics are clear: if the task fails, the exception is ignored. However, the same semantics do not exactly apply to Task, since in this case the await expression needs to return a value (T code> type). It is unclear which value of T is appropriate to return while ignoring exceptions, so the current behavior is to throw ArgumentOutOfRangeException at runtime. To help catch this situation at compile time, a new warning was recently added: CA2261 ConfigureAwaitOptions.SuppressThrowing only supports non-generic tasks. This rule defaults to a warning, but I recommend setting it to an error since it will always fail when run.

Task task = Task.FromResult(13);

 // Causes a CA2261 warning at build time and an ArgumentOutOfRangeException at runtime.
 await task.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
 

Finally, in addition to await, this tag also affects synchronous blocking. Specifically, you can call .GetAwaiter().GetResult() to block the awaiter returned from ConfigureAwait. Whether using await or GetAwaiter().GetResult(), the SuppressThrowing tag causes the exception to be ignored. Previously, when ConfigureAwait only accepted a boolean parameter, you could say "ConfigureAwait configured await"; but now you have to be more specific: "ConfigureAwait returned a configured await". Now, in addition to the behavior of await, configured awaitables may also modify the behavior of blocking code. In addition to modifying the behavior of await. Today's ConfigureAwait may be a bit misleading, but it is still mainly used to configure await. Of course, blocking operations in asynchronous code is not recommended.

Task task = Task.Run(() => throw new InvalidOperationException());

 // Synchronous blocking tasks (not recommended).  No exception will be thrown.
 task.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing).GetAwaiter().GetResult();
 

ConfigureAwaitOptions.ForceYielding

The last flag is the ForceYielding flag. I estimate this flag will rarely be used, but when you need it, you need it!

ForceYielding is similar to Task.Yield. Yield returns a special awaitable that always claims not to have completed yet, but immediately schedules its continuation. This means that await always executes asynchronously, yields to the caller, and then the async method continues execution as quickly as possible. The normal behavior of await is to check whether the waitable object is completed, and if it is completed, continue synchronous execution; ForceYielding prevents this synchronous behavior and forces await Execute asynchronously.

Personally, I find that forcing asynchronous behavior is most useful in unit testing. In some cases, it can also be used to avoid stack diving. It may also be useful when implementing asynchronous coordination primitives such as those in my AsyncEx library. Basically, anywhere you need to force await to run asynchronously, you can use ForceYielding to do so.

One thing I find interesting is that using await with ForceYielding will make await behave the same as in JavaScript. In JavaScript, await always produces a result, even if you pass it a resolved Promise. In C#, you can now use ForceYielding to wait for a completed task, and await behaves as if it has not yet completed, just like JavaScript's await.

static async Task Main()
 {
   Console.WriteLine(Environment.CurrentManagedThreadId); // main thread
   await Task.CompletedTask;
   Console.WriteLine(Environment.CurrentManagedThreadId); // main thread
   await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
   Console.WriteLine(Environment.CurrentManagedThreadId); // thread pool thread
 }
 

Please note that ForceYielding itself also means not to continue execution in the captured context, so it is equivalent to saying "schedule the remainder of the method to the thread pool" or "switch to the thread pool thread" .

// ForceYielding forces await to execute asynchronously.
 // The absence of ContinueOnCapturedContext means that the method will continue execution on the thread pool thread.
 // Therefore, the code after this statement will always run on the thread pool thread.
 await task.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
 

Task.Yield will resume execution in the captured context, so it is not exactly the same as just using ForceYielding. In fact, it's similar to ForceYielding with ContinueOnCapturedContext.

// The following two lines of code have the same effect
 await Task.Yield();
 await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding | ConfigureAwaitOptions.ContinueOnCapturedContext);
 

Of course, the real value of ForceYielding is that it can be applied to any task. Previously, when yielding was required, you had to either add a separate await Task.Yield() statement or create a custom awaitable object. Now that ForceYielding can be applied to any task, these operations are no longer necessary.

Extended Reading

It's great to see the .NET team still improving async/await functionality after all these years!

If you are more interested in the history and design discussion behind ConfigureAwaitOptions, you can check out the related Pull Request. Prior to release, there used to be an option called ForceAsynchronousContinuation, but it has since been removed. It has more complex use cases and basically overrides the default behavior of await to schedule the continuation of an asynchronous method as ExecuteSynchronously. Maybe a future update will add this option back, or maybe a future update will add support for ConfigureAwaitOptions to ValueTask. We can only wait and see!

ConfigureAwait in .NET 8 (stephencleary.com)

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.OK.
// The absence of ContinueOnCapturedContext means that the method will continue execution on the thread pool thread.
// Therefore, the code after this statement will always run on the thread pool thread.
await task.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);

Task.Yield will resume execution in the captured context, so it is not exactly the same as just using ForceYielding. In fact, it’s similar to ForceYielding with ContinueOnCapturedContext.

// The following two lines of code have the same effect
 await Task.Yield();
 await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding | ConfigureAwaitOptions.ContinueOnCapturedContext);
 

Of course, the real value of ForceYielding is that it can be applied to any task. Previously, when yielding was required, you had to either add a separate await Task.Yield() statement or create a custom awaitable object. Now that ForceYielding can be applied to any task, these operations are no longer necessary.

Extended Reading

It’s great to see the .NET team still improving async/await functionality after all these years!

If you are more interested in the history and design discussion behind ConfigureAwaitOptions, you can check out the related Pull Request. Prior to release, there used to be an option called ForceAsynchronousContinuation, but it has since been removed. It has more complex use cases and basically overrides the default behavior of await to schedule the continuation of an asynchronous method as ExecuteSynchronously. Maybe a future update will add this option back, or maybe a future update will add support for ConfigureAwaitOptions to ValueTask. We can only wait and see!

ConfigureAwait in .NET 8 (stephencleary.com)

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

This article is from the internet and does not represent1024programmerPosition, please indicate the source when reprinting:https://www.1024programmer.com/808582

author: admin

Previous article
Next article

Leave a Reply

Your email address will not be published. Required fields are marked *

Contact Us

Contact us

181-3619-1160

Online consultation: QQ交谈

E-mail: [email protected]

Working hours: Monday to Friday, 9:00-17:30, holidays off

Follow wechat
Scan wechat and follow us

Scan wechat and follow us

Follow Weibo
Back to top
首页
微信
电话
搜索