在webapi中使用async await的最佳实践

kri*_*hna 10 c# async-await asp.net-web-api asp.net-core

我有.NET核心Web API作为服务层.服务层具有所有EF代码.

如果有basecontroller与此代码

protected Task<IActionResult> NewTask(Func<IActionResult> callback)
{
    return Task.Factory.StartNew(() =>
    {
        try
        {
            return callback();
        }
        catch (Exception ex)
        {
            Logger.LogError(ex.ToString());
            throw;
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

在控制器操作中,我将上述方法中的所有调用包装到例如:

[HttpGet("something")]
public async Task<IActionResult> GetSomething(int somethingId)
{
    return await NewTask(() =>
    {
        var result = _somethingService.GetSomething(somethingId);

        if (result != null)
            return Ok(result);
        else
            return NotFound("Role not found");
    });
}
Run Code Online (Sandbox Code Playgroud)

这是正确的模式,考虑到明天,我可能有多个服务呼叫在行动或拨打其他Web服务.请指教.

Ste*_*ary 21

我希望我的api能够从异步等待事物中获益.以上模式将满足这些需求

不,不是的.在线程池上运行同步工作会给你带来同步异步代码的缺点,但两者都没有好处.

某些服务有一些使用entityframework核心的crud操作

目前,您的操作方法是我称之为"假异步" - 它看起来是异步的(例如,使用await),但实际上只是在后台线程上运行阻塞代码.在ASP.NET上,您需要真正的异步,这意味着您必须始终保持异步.欲了解更多有关这是为什么在ASP.NET坏了,看到我的前半部分介绍到asyncASP.NET的文章(它主要是用ASP.NET非核心的交易,但第一部分讲的同步和异步请求是有效的任何类型的服务器).

要使这真正异步,您应该从最低级别开始 - 在这种情况下,您的EFCore调用.他们都支持异步.因此,更换API调用像x.FirstOrDefault()await x.FirstOrDefaultAsync()(与同为所有创建/更新/删除等).

然后允许async/ await从那里自然生长; 编译器会指导你.你最终会得到异步方法somethingService,可以这样使用:

[HttpGet("something")]
public async Task<IActionResult> GetSomething(int somethingId)
{
  var result = await _somethingService.GetSomethingAsync(somethingId);
  if (result != null)
    return Ok(result);
  else
    return NotFound("Role not found");
}
Run Code Online (Sandbox Code Playgroud)

  • EF 6在异步和同步呼叫之间的差异可以忽略不计,但是EF Core中的异步和同步呼叫之间的差异非常大.我相信他们会在不久的将来解决这个问题,但我的建议是考虑你是否真的关心性能并使用异步调用.[here](https://github.com/aspnet/EntityFramework/issues/5816)您可以看到一些关于EF Core中异步和同步调用之间差异有多大的测试. (3认同)

Ker*_*rla 6

好的,首先,只有当你想在线程池线程上运行大量的CPU工作时才应该停止使用Task.Factory.StartNew和使用Task.Run.在你的情况下,你根本不需要它.此外,您应该记住,您应该只Task.Run在调用方法时使用,而不是在方法的实现中使用.你可以在这里阅读更多相关信息.

在您的情况下,您真正​​想要的是在您的服务中进行异步工作(我不确定您甚至需要在您的情况下提供服务)当您实际调用数据库并且您想要使用async/await时不只是在后台线程上运行一些东西.

基本上你的服务应该是这样的(如果你确定你需要服务):

class PeopleService
{
    public async Task<Person> GetPersonByIdAsync(int id)
    {
        Person randomPerson = await DataContext.People.FirstOrDefaultAsync(x => x.Id == id);
        return randomPerson;
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,您的服务现在会对数据库进行异步调用,这基本上就是您的模式应该是什么.您可以将此应用于所有操作(添加/删除/等..)

使服务异步后,您应该能够轻松地使用操作中的数据.

你的行为应该是这样的:

[HttpGet("something")]
public async Task<IActionResult> GetPerson(int id)
{
    var result = await PeopleService.GetPersonByIdAsync(id);

    if (result != null)
        return Ok(result);
    else
        return NotFound("Role not found");
}
Run Code Online (Sandbox Code Playgroud)