Подтвердить что ты не робот

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

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

Рабочий процесс:

Приложение пользовательского интерфейса → Конечная точка Web API (контроллер) → Метод вызова на уровне сервиса Web API → Вызов другого внешнего веб-сервиса. (Здесь у нас есть взаимодействия с БД и т.д.)

контроллер:

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. Пожалуйста, поделитесь своими мыслями.

4b9b3361

Ответ 1

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

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

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

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

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-интерфейсы, запросы к базе данных и т.д.) И сделайте их асинхронными на первом уровне (Service.ProcessAsync). Затем пусть async просачивается вверх, делая ваши действия контроллера асинхронными как последний шаг.

И ни при каких обстоятельствах вы не должны использовать Task.Run в этом сценарии.

Ответ 2

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

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

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

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

       return list;
    }
}

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

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

Ответ 3

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

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

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

Ответ 4

Что, если вы передаете несколько параметров методу веб-службы?

Я сделал это перед использованием делегата, но у меня нет доступа к этому коду и я не могу вспомнить точный синтаксис.

Я буду продолжать искать способ заставить это работать, но Microsoft должна предоставить лучшие примеры этого, чтобы обойти ограничение потока ATQ для входящих запросов.

Ответ 5

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

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

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

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