Эффективно использовать async / await с ASP.NET Web API

Я пытаюсь использовать async/await особенность ASP.NET в моем проекте Web API. Я не очень уверен, что это повлияет на производительность моей службы веб-API. Ниже приведен рабочий процесс и пример кода из моего приложения.

График Работы:

UI Application → Web API endpoint (controller) → метод вызова на уровне службы Web API → вызов другой внешней веб-службы. (Здесь мы имеем взаимодействия DB, так далее.)

:

public async Task<IHttpActionResult> GetCountries()
{
    var allCountrys = await CountryDataService.ReturnAllCountries();

    if (allCountrys.Success)
    {
        return Ok(allCountrys.Domain);
    }

    return InternalServerError();
}

Сервис Слой:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
    var response = _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");

    return Task.FromResult(response);
}

я протестировал вышеуказанный код и работает. Но я не уверен, что это правильное использование async/await. Пожалуйста, поделитесь своими мыслями.

4 ответов


Я не очень уверен, будет ли это иметь какое-либо значение в производительности моего API.

имейте в виду, что основным преимуществом асинхронного кода на стороне сервера масштабируемость. Это не заставит ваши запросы работать быстрее. Я покрываю несколько "должен ли я использовать async" вопросы в мой статью async ASP.NET.

Я думаю, что ваш вариант использования (вызов других API) хорошо подходит для асинхронного код, просто имейте в виду, что "асинхронный" не означает "быстрее". Лучший подход-сначала сделать свой UI отзывчивый и асинхронный; это сделает ваше приложение чувствовать быстрее, даже если это немного медленнее.

что касается кода, это не асинхронно:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
  var response = _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
  return Task.FromResult(response);
}

вам понадобится действительно асинхронная реализация, чтобы получить преимущества масштабируемости async:

public async Task<BackOfficeResponse<List<Country>>> ReturnAllCountriesAsync()
{
  return await _service.ProcessAsync<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
}

или (если ваша логика в этом методе на самом деле это просто проход):

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountriesAsync()
{
  return _service.ProcessAsync<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
}

обратите внимание, что легче работать "изнутри", а не "снаружи", как это. Другими словами, Не начинайте с действия асинхронного контроллера, а затем принудительно применяйте нижестоящие методы как асинхронные. Вместо этого определите естественно асинхронные операции (вызов внешних API, запросов к базе данных и т. д.) и сделайте их асинхронными в низкий


это правильно, но, возможно, не полезно.

поскольку нечего ждать – нет вызовов для блокировки API, которые могли бы работать асинхронно, – тогда вы настраиваете структуры для отслеживания асинхронной работы (которая имеет накладные расходы), но затем не используете эту возможность.

например, если уровень сервиса выполнял операции БД с Entity Framework, который поддерживает асинхронные вызовы:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
    using (db = myDBContext.Get()) {
      var list = await db.Countries.Where(condition).ToListAsync();

       return list;
    }
}

вы позволите рабочему потоку делать что-то еще, пока БД была запрошена (и, таким образом, смогла обработать другой запрос).

Await имеет тенденцию быть чем-то, что должно пройти весь путь вниз: очень трудно ретро-вписаться в существующую систему.


вы не используете async / await эффективно, потому что поток запроса будет заблокирован при выполнении синхронно метод ReturnAllCountries()

поток, назначенный для обработки запроса, будет праздно ждать, пока ReturnAllCountries() это работает.

если вы можете реализовать ReturnAllCountries() чтобы быть асинхронным, вы увидите преимущества масштабируемости. Это связано с тем, что поток может быть выпущен обратно в пул потоков .NET для обработки другого запроса, в то время как ReturnAllCountries() выполняется. Это позволит вашей службе иметь более высокую пропускную способность, более эффективно используя потоки.


Я бы изменил ваш уровень обслуживания на:

public Task<BackOfficeResponse<List<Country>>> ReturnAllCountries()
{
    return Task.Run(() =>
    {
        return _service.Process<List<Country>>(BackOfficeEndpoint.CountryEndpoint, "returnCountries");
    }      
}

как у вас есть, вы все еще используете свой _service.Process вызов синхронно, и получить очень мало или никакой выгоды от ожидания его.

при таком подходе вы обертываете потенциально медленный вызов в Task, начиная его и возвращая, чтобы его ждали. Теперь вы получаете преимущество ожидания Task.