我一直关注this question,我理解Peter Duniho之后流行(尽管尚未接受)答案背后的原因。具体来说,我知道等待后续长时间运行的 not 将阻止UI线程:
第二个示例在异步操作期间不会产生。相反,通过获取content.Result属性的值,可以强制当前线程等待异步操作完成。
我甚至证实了这一点,为了我自己的利益,就像这样:
private async void button1_Click(object sender, EventArgs e)
{
var value1 = await Task.Run(async () =>
{
await Task.Delay(5000);
return "Hello";
});
//NOTE: this one is not awaited...
var value2 = Task.Run(async () =>
{
await Task.Delay(5000);
return value1.Substring(0, 3);
});
System.Diagnostics.Debug.Print(value2.Result); //thus, UI freezes here after 5000 ms.
}
但是现在我想知道:你需要await
所有"等待"嵌套在最外面的等待操作中的操作?例如,我可以这样做:
private async void button1_Click(object sender, EventArgs e)
{
var value0 = await Task.Run(() =>
{
var value1 = new Func<Task<string>>(async () =>
{
await Task.Delay(5000);
return "hello";
}).Invoke();
var value2 = new Func<string, Task<string>>(async (string x) =>
{
await Task.Delay(5000);
return x.Substring(0, 3);
}).Invoke(value1.Result);
return value2;
});
System.Diagnostics.Debug.Print(value0);
}
或者我可以这样做:
private async void button1_Click(object sender, EventArgs e)
{
//This time the lambda is async...
var value0 = await Task.Run(async () =>
{
//we're awaiting here now...
var value1 = await new Func<Task<string>>(async () =>
{
await Task.Delay(5000);
return "hello";
}).Invoke();
//and we're awaiting here now, too...
var value2 = await new Func<string, Task<string>>(async (string x) =>
{
await Task.Delay(5000);
return x.Substring(0, 3);
}).Invoke(value1);
return value2;
});
System.Diagnostics.Debug.Print(value0);
}
他们都没有冻结UI。哪个更好?
答案 0 :(得分:13)
最后一个是优选的(尽管非常混乱)
在TAP(基于任务的异步模式)中任务(和其他等待的)表示异步操作。您基本上有3种处理这些任务的选项:
DoAsync().Result
,DoAsync().Wait()
) - 阻止调用线程,直到任务完成。使您的应用程序更加浪费,可扩展性更低,响应更少,并且容易出现死锁。 await DoAsync()
) - 不阻止调用线程。它基本上将await
之后的工作注册为等待任务完成后继续执行的工作。DoAsync()
) - 不阻止调用线程,但也不等待操作完成。您不知道处理DoAsync
时抛出的任何异常具体来说,我知道不等待后续的长时间运行将阻止UI线程
所以,不完全。如果你根本不等,什么都不会阻止但是你无法知道操作何时或是否成功完成。但是,如果您同步等待,则会阻止调用线程,如果您阻止UI线程,则可能会出现死锁。
结论:只要可能,你就应await
等待你(例如,Main
)。这包括“嵌套async-await
操作”。
关于您的具体示例:Task.Run
用于将CPU绑定的工作卸载到ThreadPool
线程,这似乎不是您试图模仿的。如果我们使用Task.Delay
来表示真正的异步操作(通常是I / O绑定),我们可以使用“嵌套async-await
”而不使用Task.Run
:
private async void button1_Click(object sender, EventArgs e)
{
var response = await SendAsync();
Debug.WriteLine(response);
}
async Task<Response> SendAsync()
{
await SendRequestAsync(new Request());
var response = await RecieveResponseAsync();
return response;
}
async Task SendRequestAsync(Request request)
{
await Task.Delay(1000); // actual I/O operation
}
async Task<Response> RecieveResponseAsync()
{
await Task.Delay(1000); // actual I/O operation
return null;
}
您可以使用匿名委托而不是方法,但是当您需要定义类型并自己调用它们时,它会感到不舒服。
如果您确实需要将该操作卸载到ThreadPool
个帖子,只需添加Task.Run
:
private async void button1_Click(object sender, EventArgs e)
{
var response = await Task.Run(() => SendAsync());
Debug.WriteLine(response);
}