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

时间:2017-02-16 14:03:15

标签: c# asp.net-web-api asp.net-core async-await

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

如果有这个代码的基本控制器

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

在控制器操作中,我在上述方法中包含所有对服务的调用,例如:

[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");
    });
}

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

2 个答案:

答案 0 :(得分:20)

  

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

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

  

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

目前,您的操作方法就是我所说的&#34;假异步&#34; - 它看起来是异步的(例如,使用x.FirstOrDefault()),但实际上只是在后台线程上运行阻塞代码。在ASP.NET上,您需要真正的异步,这意味着您必须始终保持异步。有关为什么这对ASP.NET不好的更多信息,请参阅我的intro to async on ASP.NET article的前半部分(它主要处理ASP.NET非核心,但第一部分讨论同步和异步请求对任何类型都有效)服务器)。

要使这真正异步,您应该从最低级别开始 - 在这种情况下,您的EFCore调用。他们都支持异步。因此,将await x.FirstOrDefaultAsync()之类的API调用替换为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"); } 上使用异步方法,这些方法可以这样使用:

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T> { ... }

答案 1 :(得分:6)

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

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

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

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

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

使您的服务异步后,您应该可以轻松地使用操作中的数据。

你的行为应该是这样的:

[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");
}