一年后添加
在使用 async-await 一年多之后,我知道我在原始答案中写的一些关于 async 的内容是不正确的,尽管答案中的代码仍然是正确的。Hera 是两个链接,它们极大地帮助我理解了 async-await 的工作原理。
这篇采访 Eric Lippert 展示了 async-await 的一个很好的类比。在中间的某处搜索异步等待。
在这篇文章中,非常有帮助的 Eric Lippert 展示了一些关于 async-await 的良好实践
原始答案
好的,这是一个在学习过程中帮助我的完整示例。
假设您有一个速度很慢的计算器,并且您想在按下按钮时使用它。同时,您希望您的 UI 保持响应,甚至可以做其他事情。计算器完成后,您希望显示结果。
当然:为此使用 async / await,并且没有任何旧方法,例如设置事件标志和等待设置这些事件。
这是慢速计算器:
private int SlowAdd(int a, int b)
{
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
return a+b;
}
如果你想在使用 async-await 时异步使用它,你必须使用 Task.Run(...) 来异步启动它。的返回值Task.Run
是一个可等待的任务:
Task
如果您运行的函数的返回值为 void
Task<TResult>
如果您运行的函数的返回值为TResult
您可以只启动任务,执行其他操作,并在需要任务结果时键入等待。有一个缺点:
如果你想“等待”你的函数需要是异步的并返回Task
而不是void
或Task<TResult>
而不是TResult
.
这是运行慢速计算器的代码。用 async 终止异步函数的标识符是一种常见的做法。
private async Task<int> SlowAddAsync(int a, int b)
{
var myTask = Task.Run ( () => SlowAdd(a, b));
// if desired do other things while the slow calculator is working
// whenever you have nothing to do anymore and need the answer use await
int result = await myTask;
return result;
}
旁注:有些人更喜欢 Task.Factory.StartNew 而不是 Start.Run。看看 MSDN 是怎么说的:
MSDN:Task.Run 与 Task.Factory.StartNew
SlowAdd 作为异步函数启动,并且您的线程继续。一旦它需要答案,它就会等待任务。返回值是 TResult,在这种情况下是一个 int。
如果您没有任何意义可做,代码将如下所示:
private async Task`<int`> SlowAddAsync(int a, int b)
{
return await Task.Run ( () => SlowAdd(a, b));
}
注意,SlowAddAsync 被声明为 async 函数,所以使用这个 async 函数的每个人也应该是 async 并返回 Task 或 Task <TResult
>:
private async Task UpdateForm()
{
int x = this.textBox1.Text;
int y = this.textBox2.Text;
int sum = await this.SlowAddAsync(x, y);
this.label1.Text = sum.ToString();
}
async / await 的好处是您不必摆弄 ContinueWith 来等待上一个任务完成。只需使用 await,您就知道任务已完成并且您有返回值。await 之后的语句是您通常在 ContinueWith 中执行的操作。
顺便说一句,您的 Task.Run 不必调用函数,您也可以在其中放置一个语句块:
int sum = await Task.Run( () => {
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
return a+b});
然而,一个单独的函数的好处是你让那些不需要/想要/理解异步的人,可以在没有异步/等待的情况下使用该函数。
记住:
每个使用 await 的函数都应该是异步的
每个异步函数都应该返回 Task 或 Task <Tresult
>
“但我的事件处理程序无法返回任务!”
private void OnButton1_Clicked(object sender, ...){...}
你是对的,因此这是唯一的例外:
异步事件处理程序可能返回 void
因此,当单击按钮时,异步事件处理程序将保持 UI 响应:
private async void OnButton1_Clicked(object sender, ...)
{
await this.UpdateForm();
}
但是,您仍然必须将事件处理程序声明为异步
许多 .NET 函数具有返回任务或任务<TResult
> 的异步版本。
有用于 - Internet 访问 - 流读取和写入 - 数据库访问 - 等的异步功能。
要使用它们,您不必调用 Task.Run,它们已经返回 Task 和 Task <TResult
> 只需调用它们,继续做自己的事情,当您需要答案时等待 Task 并使用 TResult。
启动多个任务并等待它们完成
如果您启动多个任务并希望等待所有任务完成,请使用 Task.WhenAll(...)而不是 Task.Wait
Task.Wait 返回一个 void。Task.WhenAll 返回一个任务,因此您可以等待它。
一旦一个任务完成,返回值已经是await的返回,但是如果你await Task.WhenAll(new Task[]{TaskA,TaskB,TaskC}); 您必须使用 Task <TResult
>.Result 属性来了解任务的结果:
int a = TaskA.Result;
如果其中一项任务引发异常,则将其作为 InnerExceptions 包装在 AggregateException 中。因此,如果您等待 Task.WhenAll,请准备好捕获 AggregateException 并检查 innerExceptions 以查看您启动的任务引发的所有异常。使用函数 AggregateException.Flatten 可以更轻松地访问异常。
有趣的阅读取消:
MSDN 关于托管线程中的取消
最后:您使用 Thread.Sleep(...)。异步版本是 Task.Delay(TimeSpan):
private async Task`<int`> MySlowAdd(int a, int b)
{
await Task.Delay(TimeSpan.FromSeconds(5));
return a+b;
}
如果您使用此功能,您的程序将保持响应。