From e5857bfd16e0d03e65362398e296ddae62c9ec81 Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Thu, 12 Mar 2020 13:05:31 -0700 Subject: [PATCH 1/4] Add `ConfigureAwait(false) information and examples. (davidfowl/AspNetCoreDiagnosticScenarios#31) --- AsyncGuidance.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/AsyncGuidance.md b/AsyncGuidance.md index 5ec903c..588e705 100644 --- a/AsyncGuidance.md +++ b/AsyncGuidance.md @@ -593,8 +593,86 @@ public async Task DoSomethingAsync() ## ConfigureAwait -TBD +`ConfigureAwait` is used to modify the awaitable struct returned by the awaitable method returning a `Task`. +It changes whether the original thread context or thread scheduler is used when resuming the continuation after `await`. +In practice, this means that code executing after `await` may or may not have access to global objects like `HttpContext`, or `SynchronizationContext.Current` may not be on the UI thread of a WPF application. + +`ConfigureAwait` takes a boolean value of `true` or `false`: +* `ConfigureAwait(true)` will cause the awaitable capture the context. This is the default behavior; ConfigureAwait(true)` is rarely used. +* `ConfigureAwait(false)` will cause the awaitable to not capture the context. + +Prefer `ConfigureAwait(false)` over the default behavior. Like `CancellationTokens`, this could be considered a cooperative effort. +By not capturing the thread context or scheduler, less memory is used, and fewer CPU cycles are used when continuations runs. +It also frees the application from having to run continuations through a `SynchronizationContext`. + + +:bulb:**NOTE By using `ConfigureAwait(false)`, code following these `await` statements cannot access certain global objects like `HttpContext`. The compiler will not catch this, but using them will result in a `NullReferenceException` during runtime. + +:bulb:**NOTE ASP.NET Core does not have a `SynchronizationContext` and does not have this problem. + +:bulb:**NOTE Some advice states to use `ContinueAwait(false)` to avoid deadlocks. This is mistaken advice and does [not guarantee against deadlocks](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html). + +❌ **BAD** This example uses a global object and throws a `NullReferenceException` during runtime. + +```C# +public async Task Get(int id) +{ + HttpResponseMessage result; + using(var client = new HttpClient()) + { + result = await client.GetAsync("http://example.org").ConfigureAwait(false); + } + + var content = await result.Content.ReadAsStringAsync().ConfigureAwait(false); + + // HttpContext is null + var username = HttpContext.Current.Session["username"]; + + return $"{username}, your requested content is {content}"; +} +``` + +:white_check_mark: **GOOD** This example avoids the `NullReferenceException` by moving the `HttpContext` access above the `await` calls. + +```C# +public async Task Get(int id) +{ + // HttpContext is not null because no awaits with ConfigureAwait(false) have executed + var username = HttpContext.Current.Session["username"]; + + HttpResponseMessage result; + using(var client = new HttpClient()) + { + result = await client.GetAsync("http://example.org").ConfigureAwait(false); + } + + var content = await result.Content.ReadAsStringAsync().ConfigureAwait(false); + + + return $"{username}, your requested content is {content}"; +} +``` + +:white_check_mark: **GOOD** This example avoids the `NullReferenceException` by not using `ConfigureAwait(false)` + +```C# +public async Task Get(int id) +{ + HttpResponseMessage result; + using(var client = new HttpClient()) + { + result = await client.GetAsync("http://example.org"); + } + + var content = await result.Content.ReadAsStringAsync(); + + // HttpContext is null + var username = HttpContext.Current.Session["username"]; + + return $"{username}, your requested content is {content}"; +} +``` # Scenarios The above tries to distill general guidance, but doesn't do justice to the kinds of real-world situations that cause code like this to be written in the first place (bad code). This section tries to take concrete examples from real applications and turn them into something simple to help you relate these problems to existing codebases. From 70f50cda9161aa334265f45eb71c46c3b3d4aa69 Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Thu, 12 Mar 2020 13:13:28 -0700 Subject: [PATCH 2/4] Fix typos and simplify verbiage for `ConfigureAwait` (davidfowl/AspNetCoreDiagnosticScenarios#31) --- AsyncGuidance.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/AsyncGuidance.md b/AsyncGuidance.md index 588e705..bb73647 100644 --- a/AsyncGuidance.md +++ b/AsyncGuidance.md @@ -593,25 +593,24 @@ public async Task DoSomethingAsync() ## ConfigureAwait -`ConfigureAwait` is used to modify the awaitable struct returned by the awaitable method returning a `Task`. -It changes whether the original thread context or thread scheduler is used when resuming the continuation after `await`. +`ConfigureAwait` changes whether the original thread context or thread scheduler is used when resuming the a method after `await`. In practice, this means that code executing after `await` may or may not have access to global objects like `HttpContext`, or `SynchronizationContext.Current` may not be on the UI thread of a WPF application. `ConfigureAwait` takes a boolean value of `true` or `false`: -* `ConfigureAwait(true)` will cause the awaitable capture the context. This is the default behavior; ConfigureAwait(true)` is rarely used. -* `ConfigureAwait(false)` will cause the awaitable to not capture the context. +* `ConfigureAwait(true)` will cause the async method to capture the context. This is the default behavior; `ConfigureAwait(true)` is rarely used. +* `ConfigureAwait(false)` will cause the async method to not capture the context. Prefer `ConfigureAwait(false)` over the default behavior. Like `CancellationTokens`, this could be considered a cooperative effort. By not capturing the thread context or scheduler, less memory is used, and fewer CPU cycles are used when continuations runs. It also frees the application from having to run continuations through a `SynchronizationContext`. -:bulb:**NOTE By using `ConfigureAwait(false)`, code following these `await` statements cannot access certain global objects like `HttpContext`. The compiler will not catch this, but using them will result in a `NullReferenceException` during runtime. +:bulb:**NOTE By using `ConfigureAwait(false)`, code following `await` statements cannot access certain global objects like `HttpContext`. The compiler will not catch this, but using them will result in a `NullReferenceException` during runtime.** -:bulb:**NOTE ASP.NET Core does not have a `SynchronizationContext` and does not have this problem. +:bulb:**NOTE ASP.NET Core does not have a `SynchronizationContext` and does not have this problem.** -:bulb:**NOTE Some advice states to use `ContinueAwait(false)` to avoid deadlocks. This is mistaken advice and does [not guarantee against deadlocks](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html). +:bulb:**NOTE Some advice states to use `ContinueAwait(false)` to avoid deadlocks. This is mistaken advice and does [not guarantee against deadlocks](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html).** ❌ **BAD** This example uses a global object and throws a `NullReferenceException` during runtime. From 7dd2d69c14c2bbea200a4f302a8528c533e023e6 Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Thu, 12 Mar 2020 13:15:53 -0700 Subject: [PATCH 3/4] Fix more typos and simplify ConfigureAwait further. (davidfowl/AspNetCoreDiagnosticScenarios#31) --- AsyncGuidance.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/AsyncGuidance.md b/AsyncGuidance.md index bb73647..2560806 100644 --- a/AsyncGuidance.md +++ b/AsyncGuidance.md @@ -593,7 +593,7 @@ public async Task DoSomethingAsync() ## ConfigureAwait -`ConfigureAwait` changes whether the original thread context or thread scheduler is used when resuming the a method after `await`. +`ConfigureAwait` changes whether the original thread context or thread scheduler is used when resuming a method after `await`. In practice, this means that code executing after `await` may or may not have access to global objects like `HttpContext`, or `SynchronizationContext.Current` may not be on the UI thread of a WPF application. @@ -606,8 +606,6 @@ By not capturing the thread context or scheduler, less memory is used, and fewer It also frees the application from having to run continuations through a `SynchronizationContext`. -:bulb:**NOTE By using `ConfigureAwait(false)`, code following `await` statements cannot access certain global objects like `HttpContext`. The compiler will not catch this, but using them will result in a `NullReferenceException` during runtime.** - :bulb:**NOTE ASP.NET Core does not have a `SynchronizationContext` and does not have this problem.** :bulb:**NOTE Some advice states to use `ContinueAwait(false)` to avoid deadlocks. This is mistaken advice and does [not guarantee against deadlocks](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html).** @@ -653,7 +651,7 @@ public async Task Get(int id) } ``` -:white_check_mark: **GOOD** This example avoids the `NullReferenceException` by not using `ConfigureAwait(false)` +:white_check_mark: **GOOD** This example avoids the `NullReferenceException` by not using `ConfigureAwait(false)`. ```C# public async Task Get(int id) From c7141c9fe6b2f841739b62db2ef7e9884e44d044 Mon Sep 17 00:00:00 2001 From: Aaron Holmes Date: Thu, 12 Mar 2020 13:17:40 -0700 Subject: [PATCH 4/4] Remove extra newline in ConfigureAwait. (davidfowl/AspNetCoreDiagnosticScenarios#31) --- AsyncGuidance.md | 1 - 1 file changed, 1 deletion(-) diff --git a/AsyncGuidance.md b/AsyncGuidance.md index 2560806..d53ebfc 100644 --- a/AsyncGuidance.md +++ b/AsyncGuidance.md @@ -646,7 +646,6 @@ public async Task Get(int id) var content = await result.Content.ReadAsStringAsync().ConfigureAwait(false); - return $"{username}, your requested content is {content}"; } ```