C# 异步/等待指南:固有签名扩散
考虑以下负责使用.NET HttpClient与API通信的类C# 异步/等待指南:固有签名扩散,c#,.net,asynchronous,async-await,C#,.net,Asynchronous,Async Await,考虑以下负责使用.NET HttpClient与API通信的类 公共类API服务代理 { 公共异步任务GetItem() { var client=新的HttpClient(); var uri=新的uri(“http://stackoverflow.com"); var response=await client.GetAsync(uri); return wait response.Content.ReadAsStringAsync(); } } 虽然简化了,但它代表了我如何编写与HTTP
公共类API服务代理
{
公共异步任务GetItem()
{
var client=新的HttpClient();
var uri=新的uri(“http://stackoverflow.com");
var response=await client.GetAsync(uri);
return wait response.Content.ReadAsStringAsync();
}
}
虽然简化了,但它代表了我如何编写与HTTP API通信的代码
现在考虑我希望在我自己的Web API项目中消费这个类。 为了简单起见,假设我有两个额外的类(直接或其他)调用这个类
- 存储库—负责调用我的服务代理,读取结果并返回某种形式的域对象
- 控制器(ASP.NET Web API控制器)是我的API的入口点,负责将数据返回给调用API的用户
我离基地很远吗?如果是的话。。。请给我指出正确的方向。我认为这些方法是异步的,但这只是由于API部分的缘故。这种异步性只是向上传播 您的
Repository.GetItem()
的第二个实现有问题,我认为:
- 在等待程序完成之前,不应调用等待程序的
方法。事实上,在我看来,在您自己的代码中直接使用等待程序唯一有意义的时候是在您实现自己的等待程序时。(这应该是非常罕见的。)在这种情况下,我认为它将与使用GetResult()
一样,但任务等待程序可能会在调用之前验证任务是否已完成(或出现故障)Task.Result
- 您不应该在API响应上使用阻塞调用来将异步API转换为同步API,而不必非常小心。根据代码的具体性质,它很容易导致死锁。。。如果阻塞调用是在单线程同步上下文中进行的,并且异步操作需要返回到相同的同步上下文,那么您的代码将等待任务完成,而任务将等待线程变为可用
Async
后缀,但这并不意味着它们必须是Async
方法。第二种可能是,但第一种可以简单地写为:
public class Controller : ApiController
{
public Task<string> GetAsync()
{
var repository = new Repository();
return repository.GetItemAsync();
}
}
公共类控制器:ApiController
{
公共任务GetAsync()
{
var repository=新存储库();
返回repository.GetItemAsync();
}
}
如果一个方法只有一个直接用于返回值的await
,那么它几乎没有什么好处
对于Repository
方法,await
将非常有用,因为在这之后您有代码要运行-您可以直接使用ContinueWith
,但这将更加痛苦。因此,我将其保留为async
/等待
版本,只是将其重命名为GetItemAsync
。。。并且可能使用ConfigureAwait(false)
来指示您实际上不需要该方法在相同的上下文中返回
我应该防止async/Wait在我的代码中变得如此丰富吗
不,我认为你最初的尝试是正确的。如果调用异步方法并需要对结果进行处理,那么使用await
是最好的方法async\wait
倾向于像这样向上传播调用堆栈
但是async\wait
,因为每个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()
{
var serviceAgent=new ApiServiceAgent();
//不需要对GetItem的结果执行任何操作,只需返回任务即可。
返回serviceAgent.GetItem();
//var apiResponse=await serviceAgent.GetItem();
//为简单/简洁起见,我的域对象是小写字符串
//返回apiResponse.ToLower();
}
}
当然,如果您需要在返回GetItem
的结果之前对其进行处理,那么该方法必须是async
然而,在您目前看到的大多数async/await“最佳实践”文章/视频中,都强调async/await应该只用于真正的异步操作
我认为通常给出此建议是为了阻止开发人员创建名称以Async
结尾的异步方法,该方法只在内部调用Task.Run
或Task.Factory.StartNew
。这具有欺骗性,因为它可以启动新线程,而真正的异步操作不会启动新线程。这就是为什么需要异步,您可以用更少的线程处理更多的异步操作
以下内容是否更能代表async/await的“最佳实践”
否。调用apiResponse.GetAwaiter().GetResult()
将阻止调用线程,直到结果可用,并可能导致GUI线程上出现错误。task.GetAwaiter().GetResult()代码>将在任务尚未完成时阻止,就像任务一样。结果
将阻止。区别在于,它将抛出第一个异常,就像wait task
抛出第一个异常一样,而不是包含所有内部异常的aggregateeexception
。在某些情况下,这可能是有用和需要的。@I3arnon:即使是这样
public class Controller : ApiController
{
public Task<string> GetAsync()
{
var repository = new Repository();
return repository.GetItemAsync();
}
}
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();
}
}