简体   繁体   中英

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

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?

PS: 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:

  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.

I think you need some reason for the background thread to stay alive while it's waiting for Method() to complete. Having an outstanding continuation is not enough to keep a thread alive, so your background worker terminates before Method() completes.

You can prove this to yourself by changing your code so that the background thread does a Thread.Sleep after the await Method() . That's almost certainly not the real behaviour you want, but if the thread sleeps for long enough you'll see Method() complete.

Following is how DoWork is raised and handled. (code retrieved using Reflector tool).

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 async method. (using async/await keyword).

To make it wait for async operation, 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 .net 2.0 construct, and async/await are .net 4.5. it will be full circle, if any one of these uses other construct.

You can't await an event handler because it doesn't return anything to await on. From the documentation of the async keyword:

The void return type is used primarily to define event handlers, where a void return type is required. The caller of a void-returning async method can't await it and can't catch exceptions that the method throws.

By adding the async keyword to the BgDoWork event handler you are instructing .NET to execute the handler asynchronously and return as soon as the first yielding operation is encountered. In this case, this happens after the first call to http.GetAsync

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM