2012-11-27 108 views
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完成。在拨打Method后立即执行BgOnRunWorkerCompleted。为什么?我在这里错过了什么?

P.S:我对这样做的替代方式或正确方法不感兴趣。我想知道这种情况下幕后发生的事情是什么?为什么不等待?

+0

你会发现我的[异步/的await简介](http://nitoprograms.blogspot.com/2012/02/async-and-await.html)很有帮助。 –

回答

7

所以,BgDoWorkBackgroundWorker

称为后台线程,它调用Method,这将启动循环,并呼吁http.GetAsync

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

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

返回同样,在BgDoWork返回另一个Task

所以的await的BackgroundWorker看到那BgDoWork返回,并假定它有完成。

然后,它提出了RunWorkerCompleted


基本上不async/await混合BackgroundWorker

+0

所以只有这样才能做到这一点,将调用后台线程中的等待,以确保它等待长时间运行的任务结束? –

+0

我无法在答案或评论中解释'async/await' - 它太复杂了。有很多在网络上的教程 - [这里有一个MSDN上(http://msdn.microsoft.com/en-gb/library/vstudio/hh191443.aspx) –

+0

这不是BackgroundWorker的是如何工作的问题。根据文档,你不能同步执行异步事件处理程序,因为它们不能返回任务 –

1

我认为你需要一些理由让后台线程在它等待Method()完成时保持活着状态。有一个优秀的延续是不足以保持线程活着,所以你的后台工作人员在Method()完成之前终止。

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

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

+0

我不知道你为什么要将反编译的'BackgroundWorker源代码到这里。 'DbDoWork()'是'async void'方法,无论如何,你都不能等待。 – svick

+0

意图是显示,异步/等待完整的链接需要等待。如果任何一个链接不在链中等待,调用方法不会等待异步方法完成。我有时遇到这个问题,这些通常在运行时遇到。通常Visual Studio会在这些情况下显示警告。关于异步无效,我们仍然可以通过获取基础任务等待,然后等待任务。 – Tilak

+0

当你所拥有的只是一个'void'返回方法时,你会如何得到这个“潜在的任务”? – svick

2

基本上有两个问题与您的代码:

  1. BackgroundWorker没有更新与async工作。而且方法async的全部重点是他们实际上第一次返回的东西还没有完成,而不是阻塞。所以,当你的方法返回(一个await后),BackgroundWorker认为已经完成,并提出RunWorkerCompleted
  2. BgDoWork()async void方法。这样的方法是“火和忘记”,你不能等待他们完成。因此,如果您运行的方法可以理解为async,那么您还需要将其更改为async Task方法。

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

1

你不能等待一个事件处理程序,因为它不返回任何东西等待上。从async关键字的文档:

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

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