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

ASP.NET Web API 2 Асинхронные действия с функцией Task.Run

Я пытаюсь сравнить (используя сканер Apache) пару конечных точек ASP.NET Web API 2.0. Один из них синхронный и один асинхронный.

        [Route("user/{userId}/feeds")]
        [HttpGet]
        public IEnumerable<NewsFeedItem> GetNewsFeedItemsForUser(string userId)
        {
            return _newsFeedService.GetNewsFeedItemsForUser(userId);
        }

        [Route("user/{userId}/feeds/async")]
        [HttpGet]
        public async Task<IEnumerable<NewsFeedItem>> GetNewsFeedItemsForUserAsync(string userId)
        {
            return await Task.Run(() => _newsFeedService.GetNewsFeedItemsForUser(userId));
        }

После просмотра презентации Стива Сандерсона я выпустил следующую команду ab -n 100 -c 10 http://localhost.... для каждой конечной точки.

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

Отправляясь на то, что Стив объяснил, я ожидал, что конечная точка async будет более результативной, потому что она немедленно выпустит потоки пула потоков обратно в пул потоков, что сделает их доступными для других запросов и улучшит пропускную способность. Но цифры кажутся точно такими же.

Что я не понимаю здесь?

4b9b3361

Ответ 1

Использование await Task.Run для создания "async" WebApi - плохая идея - вы по-прежнему будете использовать поток и даже тот же пул потоков, для запросов.

Это приведет к некоторым неприятным моментам, описанным в хороших подробностях здесь:

  • Дополнительное (ненужное) переключение потоков в поток потока Task.Run. Аналогично, когда этот поток заканчивает запрос, он должен введите контекст запроса (который не является фактическим переключателем потока, но имеет накладные расходы).
  • Создан дополнительный (ненужный) мусор. Асинхронное программирование - это компромисс: вы получаете повышенную отзывчивость за счет более высоких использование памяти. В этом случае вы создаете больше мусора для асинхронные операции, которые совершенно не нужны.
  • Эвристика пула потоков ASP.NET отбрасывается Task.Run "неожиданно" заимствует поток пула потоков. У меня нет много опыт здесь, но мой инстинкт кишки говорит мне, что эвристика должен хорошо восстановиться, если неожиданная задача действительно короткая и не обрабатывайте его так же элегантно, если неожиданная задача длится более двух секунд.
  • ASP.NET не может завершить запрос раньше, то есть, если клиент отключится или время ожидания запроса. В синхронном случае, ASP.NET знал поток запросов и мог прервать его. в асинхронный случай, ASP.NET не знает, что пул вторичных потоков thread - это "для" этого запроса. Это можно исправить, используя аннулирование токенов, но это выходит за рамки этого сообщения в блоге.

В принципе, вы не разрешаете асинхронность ASP.NET - вы просто скрываете синхронный код, привязанный к процессору, за фасадом async. Async сам по себе идеально подходит для привязанного к I/O кода, поскольку он позволяет использовать CPU (потоки) с максимальной эффективностью (без блокировки для ввода-вывода), но когда у вас есть код, связанный с вычислением, вы все равно будете иметь использовать процессор в той же степени.

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

КАК СДЕЛАТЬ ИСТИННУЮ АСЫНЦУ:

GetNewsFeedItemsForUser метод превращается в Async.

    [Route("user/{userId}/feeds/async")]
    [HttpGet]
    public async Task<IEnumerable<NewsFeedItem>> GetNewsFeedItemsForUserAsync(string userId)
    {
        return await _newsFeedService.GetNewsFeedItemsForUser(userId);
    }

Для этого:

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