如果我以昂贵的方法返回任务,我应该添加async修饰符吗?

时间:2017-03-30 23:02:43

标签: c# async-await

如果我们的方法具有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,那么如何在与{{1}相同的后台线程上执行SomeExpensiveButSynchronousMethod用于它的请求?

3 个答案:

答案 0 :(得分:3)

  

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

这太过于简单化了。请参阅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,恢复父代码所需的任何内容,使整个调用在进程中无阻塞,然后在Task完成后重新启动并返回的HttpResponse。

这根本不会发生什么。您可能会发现我的async intro有帮助。引用:

  

异步方法的开头就像任何其他方法一样执行。也就是说,它会同步运行,直到遇到“await”(或抛出异常)。

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

  

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

HttpClient不会对其请求使用后台线程,因此这部分问题没有意义。有关异步I / O如何工作的详细信息,请参阅我的博客文章There Is No Thread

回答实际问题:

  

如果我用昂贵的方法返回一个Task,我应该添加async修饰符吗?

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

答案 1 :(得分:1)

  

我理解TPL会立即启动后台线程,然后运行CallMyWebServiceMethod

不,那不是发生了什么。 async方法的第一个同步部分同步执行。如果您想确保它不阻止当前线程,您应该使用Task.Run()

答案 2 :(得分:0)

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

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