我正在使用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
。为什么会这样?我在这里缺少什么?
答案 0 :(得分:7)
因此,BgDoWork
BackgroundWorker
调用Method
,启动循环并调用http.GetAsync
GetAsync
返回Task
并继续在其他线程上工作。
您await
任务,因为Task
尚未完成,从Method
返回
同样,BgDoWork
中的等待会返回另一个Task
因此,BackgroundWorker
看到BgDoWork
已返回并认为已完成。
然后提出RunWorkerCompleted
基本上,请勿将BackgroundWorker
与async / await
混合!
答案 1 :(得分:2)
基本上,您的代码存在两个问题:
BackgroundWorker
未更新为与async
一起使用。 async
方法的全部意义在于它们实际上第一次返回它们await
尚未完成的东西,而不是阻塞。因此,当您的方法返回时(await
之后),BackgroundWorker
认为已完成并提升RunWorkerCompleted
。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
的{{1}}替代方法(它在后台线程上运行该方法并返回可用于等待的BackgroundWorker
直到它实际完成)。在Task
中await
之后,您将回到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
之后