Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

async-await

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?

like image 961
Haris Hasan Avatar asked Dec 12 '22 20:12

Haris Hasan


2 Answers

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!

like image 142
Nick Butler Avatar answered Jan 22 '23 03:01

Nick Butler


Basically, there are two problems with your code:

  1. 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.
  2. 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.

like image 38
svick Avatar answered Jan 22 '23 03:01

svick