I am playing with async await feature of C#. Things work as expected when I use it with UI thread. But when I use it in a non-UI thread it doesn't work as expected. Consider the code below
private void Click_Button(object sender, RoutedEventArgs e)
{
var bg = new BackgroundWorker();
bg.DoWork += BgDoWork;
bg.RunWorkerCompleted += BgOnRunWorkerCompleted;
bg.RunWorkerAsync();
}
private void BgOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
}
private async void BgDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
await Method();
}
private static async Task Method()
{
for (int i = int.MinValue; i < int.MaxValue; i++)
{
var http = new HttpClient();
var tsk = await http.GetAsync("http://www.ebay.com");
}
}
When I execute this code, background thread don't wait for long running task in Method
to complete. Instead it instantly executes the BgOnRunWorkerCompleted
after calling Method
. Why is that so? What am I missing here?
P.S: I am not interested in alternate ways or correct ways of doing this. I want to know what is actually happening behind the scene in this case? Why is it not waiting?
So, BgDoWork
is called on a background thread by the BackgroundWorker
It calls Method
, which starts the loop and calls http.GetAsync
GetAsync
returns a Task
and continues it's work on another thread.
You await
the Task which, because the Task
has not completed, returns from Method
Similarly, the await in BgDoWork
returns another Task
So, the BackgroundWorker
sees that BgDoWork
has returned and assumes it has completed.
It then raises RunWorkerCompleted
Basically, don't mix BackgroundWorker
with async / await
!
Basically, there are two problems with your code:
BackgroundWorker
wasn't updated to work with async
. And the whole point of async
methods is that they actually return the first time they await
something that's not finished yet, instead of blocking. So, when your method returns (after an await
), BackgroundWorker
thinks it's completed and raises RunWorkerCompleted
.BgDoWork()
is an async void
method. Such methods are “fire and forget”, you can't wait for them to complete. So, if you run your method with something that understands async
, you would also need to change it to async Task
method.You said you aren't looking for alternatives, but I think it might help you understand the problem if I provided one. Assuming that BgDoWork()
should run on a background thread and BgOnRunWorkerCompleted()
should run back on the UI thread, you can use code like this:
private async void Click_Button(object sender, RoutedEventArgs e)
{
await Task.Run((Func<Task>)BgDoWork);
BgOnRunWorkerCompleted();
}
private void BgOnRunWorkerCompleted()
{
}
private async Task BgDoWork()
{
await Method();
}
Here, Task.Run()
works as an async
-aware alternative to BackgroundWorker
(it runs the method on a background thread and returns a Task
that can be used to wait until it actually completes). After await
in Click_Button()
, you're back on the UI thread, so that's where BgOnRunWorkerCompleted()
will run. Click_Button()
is an async void
method and this is pretty much the only situation where you would want to use one: in an event handler method, that you don't need to wait on.
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