为什么后台线程不等待任务完成?

时间:2012-11-27 09:01:24

标签: c# async-await

我正在使用C#的异步等待功能。当我使用UI线程时,事情按预期工作。但是当我在非UI线程中使用它时,它不能按预期工作。请考虑以下代码

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");
        }
    }

当我执行此代码时,后台线程不会等待Method中的长时间运行任务完成。相反,它会在调用BgOnRunWorkerCompleted后立即执行Method。为什么会这样?我在这里缺少什么?

P.S:我对替代方法或纠正方法不感兴趣。我想知道在这种情况下幕后实际发生了什么?为什么不等待?

5 个答案:

答案 0 :(得分:7)

因此,BgDoWork

在后台线程上调用BackgroundWorker

调用Method,启动循环并调用http.GetAsync

GetAsync返回Task并继续在其他线程上工作。

await任务,因为Task尚未完成,从Method返回

同样,BgDoWork中的等待会返回另一个Task

因此,BackgroundWorker看到BgDoWork已返回并认为已完成。

然后提出RunWorkerCompleted


基本上,请勿将BackgroundWorkerasync / await混合!

答案 1 :(得分:2)

基本上,您的代码存在两个问题:

  1. BackgroundWorker未更新为与async一起使用。 async方法的全部意义在于它们实际上第一次返回它们await尚未完成的东西,而不是阻塞。因此,当您的方法返回时(await之后),BackgroundWorker认为已完成并提升RunWorkerCompleted
  2. BgDoWork()是一种async void方法。这种方法是“火与忘”,你不能等待它们完成。因此,如果您使用了解async的内容来运行您的方法,则还需要将其更改为async Task方法。
  3. 你说你不是在寻找替代品,但我认为如果我提供替代品,它可能会帮助你理解这个问题。假设BgDoWork()应该在后台线程上运行而BgOnRunWorkerCompleted()应该在UI线程上运行,你可以使用这样的代码:

    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();
    }
    

    此处,Task.Run()作为async的{​​{1}}替代方法(它在后台线程上运行该方法并返回可用于等待的BackgroundWorker直到它实际完成)。在Taskawait之后,您将回到UI线程,这样就会运行Click_Button()BgOnRunWorkerCompleted()Click_Button()方法,这几乎是你想要使用它的唯一情况:在事件处理程序方法中,你不需要等待。

答案 2 :(得分:1)

我认为你需要一些理由让后台线程在等待Method()完成时保持活跃状态​​。具有突出的延续不足以使线程保持活动状态,因此后台工作程序在Method()完成之前终止。

您可以通过更改代码来证明这一点,以便后台线程在Thread.Sleep后执行await Method()。这几乎肯定不是你想要的真实行为,但如果线程睡眠时间足够长,你会看到Method()完成。

答案 3 :(得分:1)

以下是DoWork的引发和处理方式。 (使用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);
    }
}

等待异步方法没有特殊处理。 (使用async / await关键字)。

要使其等待异步操作,需要进行以下更改。

async private void WorkerThreadStart(object argument)

    await this.OnDoWork(e);


async protected virtual void OnDoWork(DoWorkEventArgs e)

    await handler(this, e);

但是,BackgroundWorker是.net 2.0构造,async/await是.net 4.5。如果其中任何一个使用其他构造,它将是完整的圆圈。

答案 4 :(得分:1)

您无法等待事件处理程序,因为它不会返回任何要等待的内容。来自async关键字的文档:

  

void返回类型主要用于定义事件处理程序,其中需要void返回类型。 void返回异步方法的调用者无法等待它,也无法捕获该方法抛出的异常。

通过将async关键字添加到BgDoWork事件处理程序,您指示.NET异步执行处理程序并在遇到第一个让步操作时立即返回。在这种情况下,这发生在第一次调用http.GetAsync

之后