I would like put the code first and then explain the situation and ask my question based on that:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private async void Button_Click_2(object sender, RoutedEventArgs e) {
var result = await GetValuesAsync();
Foo.Text += result;
}
public async Task<string> GetValuesAsync() {
using (var httpClient = new HttpClient()) {
var response = await httpClient
.GetAsync("http://www.google.com")
.ConfigureAwait(continueOnCapturedContext: false);
// This is the continuation for the httpClient.GetAsync method.
// We shouldn't get back to sync context here
// Cuz the continueOnCapturedContext is set to *false*
// for the Task which is returned from httpClient.GetAsync method
var html = await GetStringAsync();
// This is the continuation for the GetStringAsync method.
// Should I get back to sync context here?
// Cuz the continueOnCapturedContext is set to *true*
// for the Task which is returned from GetStringAsync
// However, GetStringAsync may be executed in another thread
// which has no knowledge for the sync context
// because the continueOnCapturedContext is set to *false*
// for the Task which is returned from httpClient.GetAsync method.
// But, on the other hand, GetStringAsync method also has a
// chance to be executed in the UI thread but we shouldn't be
// relying on that.
html += "Hey...";
Foo.Text = html;
return html;
}
}
public async Task<string> GetStringAsync() {
await Task.Delay(1000);
return "Done...";
}
}
This is a fairly simple WPF sample which runs on .NET 4.5 and probably doesn’t make a lot of sense but this should help me explain my situation.
I have a button on the screen which has an asynchronous click event. When you look at the GetValuesAsync code, you will see the usage of await keyword twice. With the first usage, I set continueOnCapturedContext parameter of the Task.ConfigureAwait method to false. So, this indicates that I don’t necessarily want my continuation to be executed inside the SynchronizationContext.Current. So far so good.
At the second await usage (with the GetStringAsync method), I didn’t call the ConfigureAwait method. So, I basically indicated that I want to get back to current synchronization context for the continuation of the GetStringAsync method. So, as you can see, I try to set the TextBlock.Text (which belongs to UI thread) property inside the continuation.
When I run the application and click the button, I get an exception giving me the following message:
The calling thread cannot access this object because a different
thread owns it.
At first, this made no sense to me and I thought that I discovered a bug but then, I realized that GetStringAsync may be executed in another thread (highly likely) which is different than the UI thread and has no knowledge for the sync context because the continueOnCapturedContext is set to false for the Task which is returned from httpClient.GetAsync method.
Is this the case here? Also, in this case, is there a chance for GetStringAsync method to be posted back to UI thread bacuse the httpClient.GetAsync method continuation may be executed inside the UI thread?
I have also a few comments inside the code. In view of my questions and the comments inside the code, am I missing anything here?
When you call
ConfigureAwait(false), the rest of the method will be executed on a thread pool thread unless theTaskyou’reawaiting is already complete.Since
GetAsyncwill almost definitely run asynchronously, I would expectGetStringAsyncto run on a thread pool thread.The only way
GetStringAsyncwill run on the UI thread is ifGetAsynccompletes before it’s actuallyawaited. Highly unlikely.For this reason, I prefer to use
ConfigureAwait(false)for everyawaitonce the context is no longer needed.