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

Асинхронные методы ApiController - какая прибыль? Когда использовать?

(Вероятно, это дублирует вопрос ASP.NET MVC4 Async controller - зачем использовать?, но про webapi, и я не согласен с ответами там)

Предположим, что у меня длинный SQL-запрос. Его данные должны быть сериализованы в JSON и отправлены в браузер (в качестве ответа на запрос xhr). Пример кода:

public class DataController : ApiController
{
    public Task<Data> Get()
    {
        return LoadDataAsync(); // Load data asynchronously?
    }
}

Что на самом деле происходит, когда я делаю $.getJson('api/data',...) (см. этот плакат http://www.asp.net/posters/web-api/ASP.NET-Web-API-Poster.pdf):

  • [IIS] Запрос принимается IIS.
  • [IIS] IIS ждет один поток [THREAD] из управляемого пула (http://msdn.microsoft.com/en-us/library/0ka9477y(v=vs.110).aspx) и начинает работать в нем.
  • [THREAD] Webapi Создает новый объект DataController в этом потоке и других классах.
  • [THREAD] Использует task-parallel lib для запуска sql-запроса в [THREAD2]
  • [THREAD] возвращается в управляемый пул, готовый к другой обработке.
  • [THREAD2] работает с драйвером sql, считывает данные по мере готовности и вызывает [THREAD3] для ответа на запрос xhr
  • [THREAD3] отправляет ответ.

Пожалуйста, не стесняйтесь исправлять меня, если что-то не так.

В вышеприведенном вопросе говорится, что смысл и прибыль в том, что [THREAD2] не принадлежит Управляемому пулу, однако статья MSDN (ссылка выше) говорит, что

По умолчанию параллельные типы библиотек, такие как Task и Task<TResult>, используют потоки пулов потоков для запуска задач.

Итак, я делаю вывод, что все три РЕЗЬБЫ из управляемого пула.

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

Итак, какова фактическая точка переключения из 1 потока в 3 потока? Почему бы не просто увеличить потоки в пуле потоков?

Есть ли явно полезные способы использования асинхронных контроллеров?

4b9b3361

Ответ 1

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

В частности, Task, возвращаемый методом async, не запускает никакого кода. Скорее, это просто удобный способ оповестить вызывающих лиц о результате этого метода. Документы MSDN, которые вы цитируете, применимы только к задачам, которые фактически запускают код, например, Task.Run.

Кстати, плакат, на который вы ссылаетесь, не имеет ничего общего с потоками. Здесь, что происходит в запросе базы данных async (немного упрощено):

  • Запрос принимается IIS и передается в ASP.NET.
  • ASP.NET принимает один из потоков потока потока и присваивает его этому запросу.
  • WebApi создает DataController и т.д.
  • Действие контроллера запускает асинхронный запрос SQL.
  • Поток запроса возвращается в пул потоков. В настоящее время нет потоков, обрабатывающих запрос.
  • Когда результат поступает с сервера SQL, поток пула потоков считывает ответ.
  • Этот поток пула потоков уведомляет запрос о том, что он готов продолжить обработку.
  • Поскольку ASP.NET знает, что ни один другой поток не обрабатывает этот запрос, он просто присваивает тот же поток запросу, чтобы он мог завершить его напрямую.

Если вам нужен код доказательной концепции, у меня есть старый Gist, который искусственно ограничивает пул потоков ASP.NET числом (это минимальная настройка), а затем N + 1 синхронных и асинхронных запросов. Этот код просто делает задержку на секунду вместо обращения к SQL-серверу, но общий принцип тот же.

Ответ 2

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

Я полагаю, что на шаге 4 ваши шаги неверны, я не думаю, что он создаст новый поток для выполнения SQL-запроса. В 6 не создается новый поток, это только один из доступных потоков, который будет использоваться для продолжения, с которого остановился первый поток. Поток в точке 6 может быть таким же, как при запуске операции async.

Ответ 3

По моему мнению, следующее описывает явное преимущество асинхронных контроллеров над синхронными.

Веб-приложение, использующее синхронные методы для обслуживания высокой задержки вызовы, где пул потоков растет до максимального значения по умолчанию .NET 4.5. 5 000 потоков потребляли бы примерно 5 ГБ памяти больше, чем приложение может обслуживать те же запросы, что и асинхронные методы и только 50 потоков. Когда вы делаете асинхронную работу, вы не всегда используете поток. Например, когда вы делаете асинхронный запрос веб-службы, ASP.NET не будет использовать потоки между вызовом метода асинхронного вызова и ожиданием. Использование потока пул для обслуживания запросов с высокой задержкой может привести к большой памяти отпечаток и плохое использование серверного оборудования.

from Использование асинхронных методов в ASP.NET MVC 4

Ответ 4

Точка async заключается не в том, чтобы сделать приложение многопоточным, а скорее для того, чтобы однопоточное приложение выполняло что-то другое, а не ожидало ответа от внешнего вызова, выполняемого в другом потоке или процессе.

Рассмотрим настольное приложение, которое показывает цены акций на разных биржах. Приложение должно сделать пару вызовов REST/http для получения некоторых данных с каждого удаленного сервера обмена.

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

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

Для потока может быть лучше: Сделайте запрос для первого сервера, но вместо того, чтобы ждать ответа, оставляйте маркер, место, где нужно вернуться, когда цены прибудут и перейдут к выдаче второго запроса, снова оставив маркер места, куда нужно вернуться..etc.

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

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

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

Здесь есть два ключевых момента:

1) Многопоточное все еще происходит! Обработка нашего запроса цен на акции происходит в другом потоке (на другой машине). Если бы мы делали db-доступ, то это было бы правдой. В примерах, где ожидание таймера, таймер работает в другом потоке. Наше приложение, хотя и однопоточное, точка выполнения просто перескакивает (контролируемым образом), а внешние потоки выполняют

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

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

При написании веб-api, IIS или другого хоста является настольным приложением, мы пишем наши методы контроллера async, чтобы хост мог выполнять другие методы в нашем потоке для обслуживания других запросов, в то время как наш код ждет ответа от работы на другой поток/процесс/машина.