4

我正在玩 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完成。相反,它会立即执行BgOnRunWorkerCompletedafter call Method。为什么呢?我在这里想念什么?

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

4

5 回答 5

8

因此,BgDoWork由后台线程调用BackgroundWorker

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

GetAsync返回 aTask并继续它在另一个线程上的工作。

await的任务,因为Task没有完成,从Method

同样,await inBgDoWork返回另一个Task

所以,BackgroundWorker看到BgDoWork已经返回并假设它已经完成。

然后它提出RunWorkerCompleted


BackgroundWorker基本上,不要混用async / await

于 2012-11-27T09:19:59.940 回答
2

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

  1. BackgroundWorker未更新以使用async. 方法的全部意义async在于,它们实际上是在第一次返回await尚未完成的东西时返回,而不是阻塞。因此,当您的方法返回时(在 之后await),BackgroundWorker认为它已完成并 raise RunWorkerCompleted
  2. BgDoWork()是一种async void方法。这样的方法是“一劳永逸”,你不能等待它们完成。因此,如果您使用可以理解的方法运行您的方法async,您还需要将其更改为async Taskmethod.

您说您不是在寻找替代方案,但我认为如果我提供替代方案可能会帮助您理解问题。假设它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-aware 的替代方法BackgroundWorker(它在后台线程上运行该方法并返回Task可用于等待它实际完成的 a)。在awaitin之后Click_Button(),你又回到了 UI 线程,所以这就是BgOnRunWorkerCompleted()运行的地方。Click_Button()是一种async void方法,这几乎是您想要使用的唯一情况:在事件处理程序方法中,您不需要等待。

于 2012-11-27T11:01:51.097 回答
1

我认为你需要一些理由让后台线程在等待Method()完成时保持活动状态。拥有一个未完成的延续不足以保持线程处于活动状态,因此您的后台工作人员在Method()完成之前终止。

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

于 2012-11-27T09:11:35.943 回答
1

以下是DoWork是如何被提出和处理的。(使用反射器工具检索的代码)。

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。如果其中任何一个使用其他构造,它将是一个完整的圆圈。

于 2012-11-27T09:33:05.007 回答
1

您不能等待事件处理程序,因为它不会返回任何等待等待的内容。从async关键字的文档中:

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

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

于 2012-11-28T13:10:38.807 回答