I read this article https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html - however I'm seeing a contradiction:
I'm aware of the problem of deadlocking the UI thread because the UI thread blocks waiting for an async operation to complete, but the same async operation is synchronized to the UI thread context - consequently the async operation cannot enter the UI thread, so the UI thread won't stop waiting.
The article tells us the workaround is to not block on the UI thread, otherwise you need to use ConfigureAwait(false)
everywhere:
You would have to use for every await in the transitive closure of all methods called by the blocking code, including all third- and second-party code.
However later on in the article the author writes:
Preventing the Deadlock
There are two best practices (both covered in my intro post) that avoid this situation:
- In your “library” async methods, use
ConfigureAwait(false)
wherever possible.- Don’t block on Tasks; use
async
all the way down.
I'm seeing a contradiction here - in the "don't do this" section he writes that having to use ConfigureAwait(false)
everywhere would be the consequence of blocking the UI thread - but in his "best practices" list he then tells us to do just that: "use ConfigureAwait(false)
wherever possible." - though I suppose "wherever possible" would exclude third-party code, but in the case where there is no third-party code the result is the same if I block the UI thread or not.
As for my specific problem, here is my current code in a WPF MVVM project:
MainWindowViewModel.cs
private async void ButtonClickEventHandler()
{
WebServiceResponse response = await this.client.PushDinglebopThroughGrumbo();
this.DisplayResponseInUI( response );
}
WebServiceClient.cs
public class PlumbusWebServiceClient {
private static readonly HttpClient _client = new HttpClient();
public async Task<WebServiceResponse> PushDinglebopThroughGrumbo()
{
try
{
using( HttpResponseMessage response = await _client.GetAsync( ... ) )
{
if( !response.IsSuccessStatusCode ) return WebServiceResponse.FromStatusCode( response.StatusCode );
using( Stream versionsFileStream = await response.Content.ReadAsStreamAsync() )
using( StreamReader rdr = new StreamReader( versionsFileStream ) )
{
return await WebServiceResponse.FromResponse( rdr );
}
}
}
catch( HttpResponseException ex )
{
return WebServiceResponse.FromException( ex );
}
}
}
If I understand the document correctly, I should add ConfigureAwait(false)
to every await
that is not in a method that has code that needs to run on the UI thread - which is every method inside my PushDinglebopThroughGrumbo
method, but also all code in WebServiceResponse.FromResponse
(which calls await StreamReader.ReadLineAsync
). But what about any third-party code I call which also performs await
operations on the StreamReader
? I won't have access to their source-code so that would be impossible.
I'm also a bit put-off by having to place ConfigureAwait(false)
everywhere - I thought the point of the await
keyword was to eliminate explicit task library calls - shouldn't there be a different keyword for resume-context-free awaiting then? (e.g. awaitfree
).
So should my code then look like this?
MainWindowViewModel.cs
(unmodified, same as above)
WebServiceClient.cs
public class PlumbusWebServiceClient {
private static readonly HttpClient _client = new HttpClient();
public async Task<WebServiceResponse> PushDinglebopThroughGrumbo()
{
try
{
using( HttpResponseMessage response = await _client.GetAsync( ... ).ConfigureAwait(false) ) // <-- here
{
if( !response.IsSuccessStatusCode ) return WebServiceResponse.FromStatusCode( response.StatusCode );
using( Stream versionsFileStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false) ) // <-- and here
using( StreamReader rdr = new StreamReader( versionsFileStream ) )
{
return await WebServiceResponse.FromResponse( rdr ).ConfigureAwait(false); // <-- and here again, and inside `FromResponse` too
}
}
}
catch( HttpResponseException ex )
{
return WebServiceResponse.FromException( ex );
}
}
}
...I would have thought that calling ConfigureAwait(false)
would only be necessary on the topmost await
call inside the PlumbusWebServiceClient
method - i.e. the GetAsync
call.
If I do need to apply it everywhere, could I simplify it to an extension method?
public static ConfiguredTaskAwaitable<T> CF<T>(this Task<T> task) {
return task.ConfigureAwait(false);
}
using( HttpResponseMessage response = await _client.GetAsync( ... ).CF() )
{
...
}
...though this doesn't alleviate all of the fiddliness.
Update: Second example
Here is some async code I wrote that exports my application's settings to a simple text file - I can't help but think it doesn't feel right, is this really the correct way to do this?
class Settings
{
public async Task Export(String fileName)
{
using( StreamWriter wtr = new StreamWriter( fileName, append: false ) )
{
await ExportSetting( wtr, nameof(this.DefaultStatus ), this.DefaultStatus ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.ConnectionString ), this.ConnectionString ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.TargetSystem ), this.TargetSystem.ToString("G") ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.ThemeBase ), this.ThemeBase ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.ThemeAccent ), this.ThemeAccent ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.ShowSettingsButton), this.ShowSettingsButton ? "true" : "false" ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.ShowActionsColumn ), this.ShowActionsColumn ? "true" : "false" ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.LastNameFirst ), this.LastNameFirst ? "true" : "false" ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.TitleCaseCustomers), this.TitleCaseCustomers ? "true" : "false" ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.TitleCaseVehicles ), this.TitleCaseVehicles ? "true" : "false" ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.CheckForUpdates ), this.CheckForUpdates ? "true" : "false" ).ConfigureAwait(false);
}
}
private static async Task ExportSetting(TextWriter wtr, String name, String value)
{
String valueEnc = Uri.EscapeDataString( value ); // to encode line-breaks, etc.
await wtr.WriteAsync( name ).ConfigureAwait(false);
await wtr.WriteAsync( '=' ).ConfigureAwait(false);
await wtr.WriteLineAsync( valueEnc ).ConfigureAwait(false);
}
}
await Task.Run(() => this.client.PushDinglebopThroughGrumbo())
. Then eachawait
in your service code has no context to capture and you can stop worrying about it. If that code was in a library, I'd put in theConfigureAwait(false)
everywhere. – Charles Mager