【问题标题】:Should I add the async modifier if I return a Task in an expensive method?如果我以昂贵的方法返回任务,是否应该添加异步修饰符?
【发布时间】:2017-08-25 01:55:53
【问题描述】:

如果我们有一个具有async 修饰符但不使用await 运算符的方法,C# 编译器已经警告我们。

根据this 的回答,将await 添加到异步方法的末尾是没有意义的(在这种情况下,只需删除async 修饰符)。

但是,如果该方法在调用后续真正的异步方法之前需要执行昂贵的同步操作怎么办?

例如,如果我使用的是HttpClient

private readonly HttpClient client = ...

public Task<HttpResponseMessage> CallMyWebServiceMethod() {

    HttpRequestMessage request = this.SomeExpensiveButSynchronousMethod();

    return this.client.SendAsync( request );
}

这段代码会阻塞调用者(由于SomeExpensiveButSynchronousMethod)。

但是,如果我将代码更改为:

public async Task<HttpResponseMessage> CallMyWebServiceMethod() {

    HttpRequestMessage request = this.SomeExpensiveButSynchronousMethod();

    return await this.client.SendAsync( request );
}

我这样称呼它:

HttpResponse response = await myWrapper.CallMyWebServiceMethod();

...我知道 TPL 会立即启动一个后台线程,然后在后台线程中运行 CallMyWebServiceMethod,恢复父代码想要的任何内容,使整个调用在进程中非阻塞,然后在任务完成并返回HttpResponse

...如果是这样的话,那似乎是矛盾的。

如果我错了,并且调用 一直阻塞,直到它到达 SendAsync,那么我如何在 HttpClient 用于其请求的同一后台线程上执行 SomeExpensiveButSynchronousMethod ?

【问题讨论】:

  • 这确实是更具体的实现。您希望请求是异步的还是同步的(阻塞)?
  • @Tdorno 我想用我自己的操作包装HttpClient,以一种“保留”异步性的方式——假设这是我们应该做的。

标签: c# async-await


【解决方案1】:

根据这个答案,在异步方法的末尾添加等待是没有意义的(在这种情况下,只需删除异步修饰符)。

这过于简单化了。请参阅我在 eliding async and await 上的博文。

但是,如果该方法在调用后续真正的异步方法之前需要执行昂贵的同步操作怎么办?

这是一种罕见的情况,但 IMO 的适当解决方案是execute the synchronous operation synchronously (i.e., not wrapped in a Task.Run) and be sure to document its behavior

我知道 TPL 会立即启动一个后台线程,然后在后台线程中运行 CallMyWebServiceMethod,恢复父代码想要的任何内容,使整个调用在进程中不阻塞,然后在任务完成并返回之前恢复HttpResponse。

这根本不是发生的事情。您可能会发现我的async intro 很有帮助。引用:

异步方法的开头与任何其他方法一样执行。也就是说,它会同步运行,直到遇到“等待”(或引发异常)。

实际上,两个您的示例都会在执行SomeExpensiveButSynchronousMethod 时同步阻塞调用者。

如果我错了,并且调用在到达 SendAsync 之前一直处于阻塞状态,那么如何在 HttpClient 用于其请求的同一后台线程上执行 SomeExpensiveButSynchronousMethod?

HttpClient 的请求不使用后台线程,所以这部分问题没有意义。有关异步 I/O 工作原理的更多信息,请参阅我的博文There Is No Thread

回答实际问题:

如果我以昂贵的方法返回任务,是否应该添加异步修饰符?

是的。但是你应该这样做的原因不是“让它异步”;这样可以捕获来自 SomeExpensiveButSynchronousMethod 的任何异常并将其放置在返回的 Task 上,这是遵循基于任务的异步模式 (TAP) 的方法的预期语义。

【讨论】:

    【解决方案2】:

    我知道 TPL 会立即启动一个后台线程,然后运行 ​​CallMyWebServiceMethod

    不,这不是发生的事情。 async 方法的第一个同步部分同步执行。如果你想确保它不会阻塞当前线程,你应该使用Task.Run()

    【讨论】:

      【解决方案3】:

      无论哪种方式,您的昂贵方法都会阻止调用者。 async 不会神奇地为您创建线程,这就是 Task 所做的(有时)。因此,如果您添加 async 并等待 SendAsync,您只是在不必要地添加状态机开销。

      另见http://blog.stephencleary.com/2016/12/eliding-async-await.html

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-16
        相关资源
        最近更新 更多