Why is the thread in the background not waiting for the task to complete?

I play with asynchronous waiting for a C # function. Things work as expected when I use it with a UI thread. But when I use it in a thread other than the UI, it does not 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, the background thread does not wait for the completion of the lengthy task in Method . Instead, it instantly executes BgOnRunWorkerCompleted after calling Method . Why is this so? What am I missing here?

PS: I'm not interested in alternative ways or the right ways to do this. I want to know what really happens behind the scenes in this case? Why is this not waiting?

+4
source share
5 answers

So, BgDoWork is called in the background thread using BackgroundWorker

It calls Method , which starts the loop and calls http.GetAsync

GetAsync returns a Task and continues to work on another thread.

You await A task that, since Task not been completed, returns from Method

Similarly, waiting in BgDoWork returns another Task

So, BackgroundWorker sees that BgDoWork back and assumes that it is complete.

Then it raises RunWorkerCompleted


Basically, do not mix BackgroundWorker with async / await !

+7
source

Basically, there are two problems with your code:

  • BackgroundWorker not updated to work with async . And the whole point of async methods is that they actually return the first time they await something that is not finished, instead of locking. So, when your method returns (after await ), BackgroundWorker thinks it is complete and raises RunWorkerCompleted .
  • BgDoWork() is an async void method. Such methods are โ€œfire and forgetโ€; you cannot wait for their completion. So, if you use your method with async understanding, you will also need to change it to the async Task method.

You said you were not looking for an alternative, but I think it can help you understand the problem, if I have provided it. Assuming that BgDoWork() should work in the background thread, and BgOnRunWorkerCompleted() should work in the user interface thread, you can use this code:

 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 alternative to BackgroundWorker (it launches the method in the background thread and returns Task , which can be used to wait until it actually finishes). After await in Click_Button() you will return to the user interface thread, so where BgOnRunWorkerCompleted() will be launched. Click_Button() is an async void method, and this is almost the only situation you would like to use it in: an event handler method that you don't have to wait for.

+2
source

I think you need a reason the background thread stays alive while it waits for Method() . Continuous continuation is not enough to keep the stream in the stream, so your background worker ends before Method() .

You can prove it yourself by modifying your code so that the background thread Thread.Sleep after await Method() . This is almost certainly not the actual behavior you want, but if the thread sleeps long enough, you will see Method() complete.

+1
source

The following shows how DoWork gets up and handled. (code obtained using Reflector ).

 private void WorkerThreadStart(object argument) { object result = null; Exception error = null; bool cancelled = false; try { DoWorkEventArgs e = new DoWorkEventArgs(argument); this.OnDoWork(e); if (e.Cancel) { cancelled = true; } else { result = e.Result; } } catch (Exception exception2) { error = exception2; } RunWorkerCompletedEventArgs arg = new RunWorkerCompletedEventArgs(result, error, cancelled); this.asyncOperation.PostOperationCompleted(this.operationCompleted, arg); } protected virtual void OnDoWork(DoWorkEventArgs e) { DoWorkEventHandler handler = (DoWorkEventHandler) base.Events[doWorkKey]; if (handler != null) { handler(this, e); } } 

There is no special handling to wait for the async method. (using the async / await keyword).

In order to wait for asynchronous operation, the following changes are required.

 async private void WorkerThreadStart(object argument) await this.OnDoWork(e); async protected virtual void OnDoWork(DoWorkEventArgs e) await handler(this, e); 

But then BackgroundWorker is a .net 2.0 construct and async / await are .net 4.5. it will be a full circle if any of them uses a different design.

+1
source

You cannot wait for an event handler because it does not return anything to wait. From the async documentation, the keyword:

The void return type is mainly used to define event handlers where the void return type is required. The calling async method returning void cannot wait for it and cannot catch the exceptions that the method throws.

By adding the async keyword to the BgDoWork event handler, you instruct .NET to execute the asynchronous asynchronous handler and return immediately after the first input operation. In this case, this happens after the first call to http.GetAsync

+1
source

All Articles