I have a WinForms app, and I have some code that needs to run on the UI thread. However, the code after the await
runs on a different thread.
protected override async void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
// This runs on the UI thread.
mainContainer.Controls.Clear();
var result = await DoSomethingAsync();
// This also needs to run on the UI thread, but it does not.
// Instead it throws an exception:
// "Cross-thread operation not valid: Control 'mainContainer' accessed from a thread other than the thread it was created on"
mainContainer.Controls.Add(new Control());
}
I also tried explicitly adding ConfigureAwait(true)
, but it makes no difference. My understanding was that if I omit ConfigureAwait(false)
, then the continuation should run on the original thread. Is this incorrect in some situations?
I've also noticed that if I add a control to the collection before the await, then the continuation magically runs on the correct thread.
protected override async void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
// This runs on the UI thread.
mainContainer.Controls.Add(new Control());
mainContainer.Controls.Clear();
var result = await DoSomethingAsync();
// This also runs on the UI thread now. Why?
mainContainer.Controls.Add(new Control());
}
My question is:
For reference, here are the important parts of DoSomethingAsync
. It submits an HTTP request using RestSharp.
protected async Task DoSomethingAsync()
{
IRestRequest request = CreateRestRequest();
// Here I await the response from RestSharp.
// Client is an IRestClient instance.
// I have tried removing the ConfigureAwait(false) part, but it makes no difference.
var response = await Client.ExecuteTaskAsync(request).ConfigureAwait(false);
if (response.ResponseStatus == ResponseStatus.Error)
throw new Exception(response.ErrorMessage ?? "The request did not complete successfully.");
if (response.StatusCode >= HttpStatusCode.BadRequest)
throw new Exception("Server responded with an error: " + response.StatusCode);
// I also do some processing of the response here; omitted for brevity.
// There are no more awaits.
}
My understanding was that if I omit ConfigureAwait(false), then the continuation should run on the original thread. Is this incorrect in some situations?
What actually happens is that await
will capture the current context by default, and use this context to resume the async
method. This context is SynchronizationContext.Current
, unless it is null
, in which case it is TaskScheduler.Current
(usually the thread pool context). Most of the time, the UI thread has a UI SynchronizationContext
- in the case of WinForms, an instance of WinFormsSynchronizationContext
.
I've also noticed that if I add a control to the collection before the await, then the continuation magically runs on the correct thread.
No thread starts with a SynchronizationContext
automatically. The WinForms SynchronizationContext
is installed on-demand when the first control is created. This is why you're seeing it resume on a UI thread after creating a control.
Since moving to OnLoad
is a workable solution, I recommend you just go with that. The only other option (to resume on the UI thread before a control is created) is to manually create a control before your first await
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With