如何使任务可等待
昨天我开始使用 Microsoft CTP 异步库,但我找不到可等待任务的正确实现。我知道它必须有这样的实现?:
public struct SampleAwaiter<T>
{
private readonly Task<T> task;
public SampleAwaiter(Task<T> task) { this.task = task; }
public bool IsCompleted { get { return task.IsCompleted; } }
public void OnCompleted(Action continuation) { TaskEx.Run(continuation); }
public T GetResult() { return task.Result; }
}
但是我现在如何实现一个任务,比方说,等待 5 秒,然后返回一些字符串,例如“Hello World”?
一种方法是直接使用 Task,如下所示:
Task<string> task = TaskEx.Run(
() =>
{
Thread.Sleep(5000);
return "Hello World";
});
string str = await task;
但是我将如何使用可等待的实现来做到这一点?或者我只是误解了一切?
感谢您提供任何信息/帮助:)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这里的关键是
AsyncCtpThreadingExtensions.GetAwaiter
它通过扩展方法提供这些方法。由于异步实现是基于模式的(如LINQ),而不是绑定到特定的接口,它可以来自任何地方(在本例中是TaskAwaiter
)。您编写的代码值得等待。例如:
5 秒后打印
Hello World
。The key here is
AsyncCtpThreadingExtensions.GetAwaiter
which provides those methods via an extension method. Since the async implementation is pattern based (like LINQ), rather than tied to a specific interface it can come from everywhere (it isTaskAwaiter
in this case).Your code as written is awaitable. For example:
This prints
Hello World
after 5 seconds.一年后添加
使用 async-await 一年多了,我知道我在原来的答案中写的关于 async 的一些内容不正确,尽管答案中的代码仍然是正确的。 Hera 有两个链接极大地帮助我理解了 async-await 的工作原理。
这次采访 Eric Lippert 展示了 async-await 的一个很好的类比。在中间的某个位置搜索 async-await。
在本文中,非常乐于助人的 Eric Lippert 展示了一些异步的良好实践-await
原始答案
好的,这是一个在学习过程中对我有帮助的完整示例。
假设您有一个速度较慢的计算器,并且您想在按下按钮时使用它。同时,您希望您的 UI 保持响应能力,甚至可能做其他事情。当计算器完成后,您想要显示结果。
当然:为此使用 async/await,而不是像设置事件标志和等待设置这些事件这样的旧方法。
这是慢速计算器:
如果您想在使用 async-await 时异步使用它,则必须使用 Task.Run(...) 异步启动它。
Task.Run
的返回值是一个可等待的任务:Task
如果您运行的函数的返回值为 voidTask
如果您运行的函数的返回值是TResult
您可以启动任务,执行其他操作,并且每当您需要任务的结果时,您可以键入await。有一个缺点:
如果您想“等待”,您的函数需要异步并返回
Task
而不是void
或Task
而不是TResult
。这是运行慢速计算器的代码。
使用 async 终止异步函数的标识符是常见的做法。
SlowAdd 作为异步函数启动,并且您的线程继续。一旦它需要答案,它就会等待任务。返回值是 TResult,在本例中是一个 int。
如果您没有什么有意义的事情要做,代码将如下所示:
请注意,SlowAddAsync 被声明为异步函数,因此使用此异步函数的每个人也应该是异步的并返回 Task 或 Task> ;:
async/await 的好处是你不必摆弄ContinueWith 来等待上一个任务完成。只需使用await,您就知道任务已完成并且您有返回值。 wait 之后的语句是您通常在ContinueWith 中执行的操作。
顺便说一句,你的 Task.Run 不必调用函数,你也可以在其中放置一个语句块:
然而,单独函数的好处是,你可以为那些不需要/想要/理解异步的人提供帮助,可以在没有 async/await 的情况下使用该函数。
记住:
“但是我的事件处理程序无法返回任务!”
你是对的,因此这是唯一的例外:
因此,当单击按钮时,异步事件处理程序将使 UI 保持响应:
但是,您仍然必须声明事件处理程序异步
有异步函数
- 互联网接入
- 流读写
- 数据库访问
- 等等。
要使用它们,您不必调用 Task.Run,它们已经返回 Task 和 Task>只需打电话给他们,继续做你自己的事情,当你需要等待任务的答案并使用 TResult 时。
启动多项任务并等待它们完成
如果您启动多个任务并且想要等待所有任务完成,请使用 Task.WhenAll(...) NOT Task.Wait
Task.Wait 返回 void。 Task.WhenAll 返回一个 Task,因此您可以等待它。
一旦一个任务执行完毕,返回值已经是await的返回了,但是如果你await Task.WhenAll(new Task[]{TaskA, TaskB, TaskC});>.Result 属性来了解任务的结果:
您必须使用 Task
如果其中一个任务引发异常,则会将其包装为 AggregateException 中的 InnerException。因此,如果您等待 Task.WhenAll,请准备好捕获 AggregateException 并检查 innerExceptions 以查看您启动的任务引发的所有异常。使用 AggregateException.Flatten 函数可以更轻松地访问异常。
有关取消的有趣阅读:
MSDN 关于托管中的取消线程
最后:您使用 Thread.Sleep(...)。异步版本是 Task.Delay(TimeSpan):
如果您使用此函数,您的程序将保持响应。
Addition one year later
After using async-await for over a year now, I know that some things about async I wrote in my original answer are not correct, although the code in the answer is still correct. Hera are two links that helped me enormously to understand how async-await works.
This interview Eric Lippert shows an excellent analogy for async-await. Search somewhere in the middle for async-await.
In this article, the ever so helpful Eric Lippert shows some good practices for async-await
Original answer
OK, here is a full example that helped me during the learning process.
Suppose you have a slow calculator, and you want to use it when pressing a button. Meanwhile you want your UI to stay responsive, and maybe even do other things. When the calculator is finished you want to display the result.
And of course: use async / await for this, and none of the old methods like setting event flags and waiting for these events to be set.
Here is the slow calculator:
If you want to use this asynchronously while using async-await you have to use Task.Run(...) to start it asynchronously. The return value of
Task.Run
is an awaitable Task:Task
if the return value of the function you Run is voidTask<TResult>
if the return value of the function you run isTResult
You can just start the Task, do something else, and whenever you need the result of the Task you type await. There is one drawback:
If you want to 'await' your function needs to be async and return
Task
instead ofvoid
orTask<TResult>
instead ofTResult
.Here's the code that runs the slow calculator.
It's common practice to terminate the identifier of an async function with async.
The SlowAdd is started as an async function, and your thread continues. Once it needs the answer it awaits for the Task. The return value is the TResult, which in this case is an int.
If you have nothing meaningful to do the code would look like this:
Note that SlowAddAsync is declared an async function, so everyone who uses this async function should also be async and return Task or Task
<TResult
>:The nice thing about async / await is that you don't have to fiddle with ContinueWith to wait until the previous task is finished. Just use await, and you know the task is finished and you have the return value. The statement after the await is what you'd normally do in the ContinueWith.
By the way, your Task.Run doesn't have to call a function, you can also put a statement block in it:
However the nice thing about a separate function is that you give those who don't need / want / understand async, the possibility to use the function without async / await.
Remember:
"But my event handler can't return a Task!"
You're right, therefore that is the only exception:
So when clicking the button, the async event handler would keep the UI responsive:
However you still have to declare the event handler async
There are async functions for
- Internet access
- Stream Read and Write
- Database access
- etc.
To use them you don't have to call Task.Run, they already return Task and Task
<TResult
> just call them, continue doing your own stuff and when you need the answer await for the Task and use the TResult.Start several tasks and wait for them to finish
If you start several tasks and you want to wait for all of them to finish, use Task.WhenAll(...) NOT Task.Wait
Task.Wait returns a void. Task.WhenAll returns a Task, so you can await for it.
Once a task is finished, the return value is already the return of the await, but if you await Task.WhenAll ( new Task[]{TaskA, TaskB, TaskC});
you have to use the Task
<TResult
>.Result property to know the result of a task:If one of the tasks throws an exception, it is wrapped as InnerExceptions in an AggregateException. So if you await Task.WhenAll, be prepared to catch the AggregateException and check the innerExceptions to see all exceptions thrown by the tasks you started. Use the function AggregateException.Flatten to access the exceptions more easily.
Interesting to read about cancellation:
MSDN about Cancellation in managed threads
Finally: you use Thread.Sleep(...). The async version is Task.Delay(TimeSpan):
If you use this function your program keeps responsive.
我最终得到了这个示例代码......这是可等待模式的正确实现吗?
I ended up with this sample code ... is this the proper implementation of the awaitable pattern?