为什么要在 Task<> 中使用 async 和 await?

15

如果我有一个普通的方法,我想将其变成异步方法:

public int Foo(){}

我会这样做:

public Task<int> FooAsync(){
    return Task.Run(() => Foo());
}

为什么我要这样做:

public async Task<int> FooAsync(){
    return await Task.Run(() => Foo());
}
我打算使用它的方式是:
FooAsync().ContinueWith((res) => {});

我希望这个方法可以持续运行而不停止,但我想要像回调一样触发某些操作,因此使用了ContinueWith。但是对于第二个版本,使用它有意义吗?

2个回答

13
在我看来,只有当你编写一个方法内部涉及异步调用时,并且希望它“看起来”像顺序代码时,才需要使用asyncawait。换句话说,您想要编写和阅读类似于正常顺序代码的代码,处理异常就像处理正常顺序代码一样,返回值就像处理正常顺序代码一样,等等。接着,编译器的责任是以保留逻辑的方式重写该代码并具备必要的回调。
你的方法非常简单,我认为根本不需要进行这种重写,只需返回任务即可,但是使用它的人可能希望await其返回值。

谢谢你,@paulsm4。我必须说我只是在重复最近听到的东西,我想这是来自http://hanselminutes.com/327/everything-net-programmers-know-about-asynchronous-programming-is-wrong,但不是100%确定。我对异步特性还没有太多经验,所以我可能错了,我很乐意学习更多,也许异步在这种情况下提供了一些好处。 - Massimiliano
2
此外,您可能想阅读《我应该为同步方法公开异步包装器吗?》(http://blogs.msdn.com/b/pfxteam/archive/2012/03/24/10287244.aspx)(答案几乎总是“不需要”;如果您有一个同步方法,则给它一个同步方法签名)。 - Stephen Cleary
没有提及IO,这个答案远远不足以成为完整的答案。 - spender
@Yacoder关于async和await的用途是正确的。这里有一个很好的例子说明何时使用它 - strangeloops

5

Why would I do:

 public async Task<int> FooAsync()
 {
  return await Task.Run(() => Foo());
 }
你不需要这样做。你之前的版本返回的是Task,它本身就可以被await。这个新版本没有增加任何东西。

The way I plan to use this is:

  FooAsync().ContinueWith((res) => {});

I want the method to just run without stopping, but I want something like a callback to be fired, hence the ContinueWith.

这就是区别所在。让我们更详细地阐述一下您的例子:

void DoAsyncStuff()
{
    FooAsync().ContinueWith((res) => { DoSomethingWithInt(res.Result) });
}

使用 async,您可以像这样编写相同的代码:
void async DoAsyncStuff()
{
    int res = await FooAsync();
    DoSomethingWithInt(res);
}

结果是一样的。await关键字将你方法剩下的部分转换成一个继续执行的任务,该任务在FooAsync返回结果后被恢复执行。这与你的其他代码类似,但更易读。*耸肩*

你的async方法不应该返回void。只有当它们将作为事件处理程序时,async方法才应该返回void。如果方法中没有返回值,则应返回Task。除此之外,这是一个相当不错的帖子。 - Servy
@Servy "异步方法只有在它们将成为事件处理程序时才应该返回void。" 这里是这种情况,对吗?在这个例子中,DoAsyncStuff 应该返回什么值,并且在不编写多余代码的情况下从哪里获取该值?此外,如果异步方法返回 Task 并且您等待它,您会收到编译器警告(CS4014),这很烦人。我最近在这里询问了这个问题。如果您有更好的答案,请在那里发布。 - Mud
当一个异步方法返回void,它本质上是一个"fire and forget"方法。调用方无法知道该方法实际完成的时间。此处OP的代码没有表明他想要这样做。至于编译器警告,那是因为你几乎肯定不想这样做,就像你在链接的帖子中告诉你的一样;警告正确地指出了设计上的缺陷,并且很可能你并不想得到你正在获取的行为。尽管你可能希望在某些极端情况下获得该行为,但我仍然坚持我的主张。 - Servy
无法知道方法实际完成的时间。当FooAsync返回时,它会调用回调函数(如OP示例中所示),或通过await关键字恢复编译器创建的回调函数。无论哪种方式,需要知道它的代码都会被调用。OP的代码没有表明他想要什么。他说他想要这样的东西:“我希望像回调一样触发某些事件”。不想做什么?就像我之前说的,在Stackoverflow上问了这个问题。如果你有更好的答案,能否在那里详细说明,这样你就有更多的空间了? - Mud
  1. 调用 DoAsyncStuff 方法的方法将无法知道 DoSomethingWithInt 何时完成运行。这就是我所说的你引用的语句的意思。如果调用者想要执行在您的 continuation 之后运行的 continuation,除非该方法返回一个 Task,否则他不能这样做。
  2. 那不是我所指的。我说过,如果它是事件处理程序,那么 async 方法应该只返回 void,而你说:"这里是这种情况,对吧?" 没有迹象表明这是真的。
  3. 再次强调,这不是我所指的。OP 几乎肯定不想点火并忘记。
- Servy
调用DoAsyncStuff方法的方法将无法知道DoSomethingWithInt何时完成。处理包含在该方法中,就像OP的示例一样。OP几乎肯定不想要“fire and forget”。他的示例将需要通知响应的代码放在闭包中。async让他将其移出闭包(进入编译器生成的闭包)。在某个点上,堆栈中总会有一个不能await且未返回Task的非异步方法,对吗?如果我错了,请你能否在我提到的帖子中表达你的意思? - Mud

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接