考虑以下类负责使用.NET HttpClient与API通信。
public class ApiServiceAgent
{
public async Task<string> GetItem()
{
var client = new HttpClient();
var uri = new Uri("http://stackoverflow.com");
var response = await client.GetAsync(uri);
return await response.Content.ReadAsStringAsync();
}
}
虽然简化了,但它代表了我如何编写与HTTP API通信的代码。
现在考虑我希望在我自己的Web API项目中使用这个类。 为简单起见,我可以说我有两个额外的类(直接或其他方式)调用此类。
这两个类可以看起来像这样:
public class Controller : ApiController
{
public async Task<string> Get()
{
var repository = new Repository();
return await repository.GetItem();
}
}
public class Repository
{
public async Task<string> GetItem()
{
var serviceAgent = new ApiServiceAgent();
var apiResponse = await serviceAgent.GetItem();
//For simplicity/brevity my domain object is a lowercase string
return apiResponse.ToLower();
}
}
在上面的两个类中,async / Task方法签名在内部传播到Controller一直很常见......在你看到的async / await主题的大多数示例.NET代码中很常见。
然而,在大多数异步/等待&#34;最佳实践&#34;你看到的文章/视频强调async / await应仅用于真正的异步操作。
我认为存储库或控制器都不会做任何真正异步的事情 - 这都是在服务代理中完成的。
我是否应该阻止async / await在我的代码中变得如此多产?
以下内容是否更能代表&#34;最佳实践&#34;异步/等待?
public class Controller : ApiController
{
public string Get()
{
var repository = new Repository();
return repository.GetItem();
}
}
public class Repository
{
public string GetItem()
{
var serviceAgent = new ApiServiceAgent();
var apiResponseTask = serviceAgent.GetItem();
var apiResponse = apiResponse.GetAwaiter().GetResult();
return apiResponse.ToLower();
}
}
我离开基地了吗?如果是这样的话,请指出我正确的方向。
答案 0 :(得分:7)
我认为这些方法是异步的 - 但只能凭借API部分。那种异步只会向上传播。
Repository.GetItem()
的第二次实施是有问题的,IMO:
GetResult()
方法。事实上,在我看来,在你自己的代码中直接使用awaiter是唯一有意义的时候就是你实现了自己的等待者。 (这应该是非常罕见的。)在这种情况下,我认为它与使用Task<T>.Result
的做法相同,但是任务等待可能验证了在打电话之前,任务已经完成(或出现故障)。这表明您的方法应该具有Async
后缀,但这并不意味着它们需要async
方法。第二种可能会这样做,但第一种可以简单地写成:
public class Controller : ApiController
{
public Task<string> GetAsync()
{
var repository = new Repository();
return repository.GetItemAsync();
}
}
如果方法async
只有一个await
直接用于返回值,那么很少有好处。
对于Repository
方法,await
会很有用,因为您在完成后需要运行代码 - 您可以直接使用ContinueWith
,但这样会更多痛苦。因此,我将其保留为async
/ await
版本,只需将其重命名为GetItemAsync
...并可能使用ConfigureAwait(false)
表示您没有&#39} ; t实际上需要该方法返回相同的上下文。
答案 1 :(得分:2)
我是否应该阻止async / await在我的代码中变得如此多产?
没有。我认为你最初的尝试是在正确的轨道上。如果您调用异步方法并且需要对结果执行某些操作,那么使用await
是最好的方法。 async\await
倾向于像这样传播调用堆栈。
但是async\await
has a cost,因为每个async
方法都会生成一个状态机。如果您不需要异步操作的结果但只是将其返回给调用者,那么您只需返回被调用async
方法的结果以避免生成状态机。
例如:
public class Repository
{
public Task<string> GetItem()
{
var serviceAgent = new ApiServiceAgent();
// Don't need to do anything with the result of GetItem, just return the task.
return serviceAgent.GetItem();
//var apiResponse = await serviceAgent.GetItem();
//For simplicity/brevity my domain object is a lowercase string
//return apiResponse.ToLower();
}
}
当然,如果您在返回之前需要对GetItem
的结果执行某些操作,则该方法必须为async
。
然而,在大多数异步/等待&#34;最佳实践&#34;你看到的文章/视频强调async / await应该仅用于真正的异步操作。
我认为通常这个建议是为了阻止开发人员创建名称以Async
结尾的异步方法,只需在其中调用Task.Run
或Task.Factory.StartNew
。这是欺骗性的,因为它可以启动一个新线程,而真正的异步操作不会启动一个新线程。这就是使异步变得如此理想的原因,你可以用更少的线程来处理更多的异步操作。
以下内容是否更能代表&#34;最佳实践&#34; async / await?
没有。调用apiResponse.GetAwaiter().GetResult();
将阻塞调用线程,直到结果可用,并且它可以在GUI线程上导致deadlock。