异步/等待指导:内在特征增殖

Gav*_*orn 4 .net c# asynchronous async-await

请考虑以下类,该类负责使用.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();
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然简化了,但它代表了我如何编写与HTTP API通信的代码.

现在考虑我希望在我自己的Web API项目中使用这个类.为简单起见,我可以说我有两个额外的类(直接或其他方式)调用此类.

  • 存储库 - 负责我调用我的服务代理,读取结果并返回某种形式的Domain对象.
  • 控制器 - ASP.NET Web API控制器 - 我的API的入口点,负责将数据返回给调用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的两个类中,async/Task方法签名已经完全内在地传播到Controller ...在你看到的async/await主题的大多数示例.NET代码中都很常见.

但是,在大多数异步/等待"最佳实践"文章/视频中,您会看到当前强调async/await应用于真正的异步操作.

我认为存储库或控制器都不会做任何真正异步的事情 - 这都是在服务代理中完成的.

我是否应该阻止async/await在我的代码中变得如此多产?

以下是否更能代表"最佳实践"async/await?

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我离开基地了吗?如果是这样的话,请指出我正确的方向.

Jon*_*eet 7

我认为这些方法异步的 - 但仅限于API部分.那种异步只会向上传播.

你的第二个实现Repository.GetItem()是有问题的,IMO:

  • 你应该叫awaiter的GetResult()的awaiter完成之前的方法.事实上,在我自己的代码中直接使用awaiter是唯一合理的时间是在我看来你实现自己的awaiter时.(这应该是非常罕见的.)在这种情况下,我认为它将与使用相同Task<T>.Result,但任务执行者可能会在调用之前验证任务已完成(或出现故障).
  • 应该对API响应使用阻塞调用来将异步API转换为同步API而不需要太多关注.根据代码的确切性质,它很容易导致死锁...如果在单线程同步上下文中进行阻塞调用并且异步操作需要返回到同一个同步上下文,那么你将结束等待任务完成的代码,以及等待线程可用的任务.

这表明你的方法应该有一个Async后缀,但这并不意味着它们需要是async方法.第二种可能会这样做,但第一种可以简单地写成:

public class Controller : ApiController
{
    public Task<string> GetAsync()
    {
        var repository = new Repository();
        return repository.GetItemAsync();
    }
}
Run Code Online (Sandbox Code Playgroud)

制作一个方法很少有好处,async只要它有一个await直接用于返回值的方法.

对于你的Repository方法,这await将是有用的,因为你有完成后运行的代码 - 你可以ContinueWith直接使用,但这将更痛苦.所以我将其保留为async/ awaitversion,只需将其重命名为GetItemAsync...并且可能ConfigureAwait(false)用于表示您实际上不需要该方法返回相同的上下文.

  • `task.GetAwaiter().GetResult();`将在任务尚未完成时阻止`task.Result`.区别在于它将抛出第一个异常,就像`await task`而不是包含所有内部异常的`AggregateException`一样.在某些情况下,这可能是有用的和期望的. (3认同)