Как асинхронные методы работают в C #?
-
06-07-2019 - |
Вопрос
Я использую асинхронные методы в некоторых своих проектах, и мне это нравится, так как это позволяет моему приложению быть намного более масштабируемым.Однако мне интересно, как асинхронные методы действительно работают в фоновом режиме?Как .NET (или Windows?) узнает, что вызов завершен?В зависимости от количества выполненных мной асинхронных вызовов я вижу, что создаются новые потоки (хотя и не всегда ...).Почему?
Кроме того, я хотел бы отслеживать, сколько времени занимает выполнение запроса.Чтобы протестировать концепцию, я написал следующий код, который асинхронно вызывает веб-службу и сразу после этого запускает секундомер.
for (int i = 0; i < 10000; i++)
{
myWebService.BeginMyMethod(new MyRequest(), result, new AsyncCallback(callback), i);
stopWatches[i].Start();
}
// Call back stop the stopwatch after calling EndMyMethod
Это не работает, поскольку все запросы (10000) имеют одинаковое время начала, а длительность будет увеличиваться линейно (вызов 0 = длительность 1, вызов 1 = длительность 2 и т.д.).Как я мог бы отслеживать реальную продолжительность вызова с помощью асинхронного метода (с момента реального выполнения запроса до его завершения)?
Обновить:Блокирует ли асинхронный метод поток?Я понимаю, что он использует .NET ThreadPool
но как IAsyncResult
знайте, что вызов завершен и пришло время вызвать CallBack
способ?
Решение
Код - это железная дорога, а нить - это поезд.Когда поезд едет по железной дороге, он выполняет код.
BeginMyMethod
выполняется основным потоком.Если вы заглянете внутрь BeginMyMethod
он просто добавляет делегата MyMethod
к тому ThreadPool
очередь.Фактический MyMethod
выполняется одним из поездов железнодорожного пула.Процедура завершения, которая вызывается, когда MyMethod
is done выполняется тем же потоком, который выполнил MyMethod
, а не вашим основным потоком, который выполняет остальную часть кода.Пока поток пула потоков занят выполнением MyMethod
, основной поток может либо задействовать какую-то другую часть железнодорожной системы (выполнить какой-то другой код), либо просто находиться в спящем режиме, ожидая, пока загорится определенный семафор.
Следовательно, не существует такого понятия, как IAsyncResult
"зная", когда вызывать процедуру завершения, вместо этого процедура завершения - это просто делегат, вызываемый потоком пула потоков сразу после завершения выполнения MyMethod
.
Я надеюсь, вы не возражаете против несколько детской аналогии с поездом, я знаю, что она не раз помогала мне, когда я объяснял людям многопоточность.
Другие советы
Суть этого в том, что призвание Begin
ставит в очередь запрос на выполнение вашего метода.Метод фактически выполняется в ThreadPool, который представляет собой набор рабочих потоков, предоставляемых средой выполнения.
Пул потоков - это фиксированный набор потоков для выполнения асинхронных задач по мере их постановки в очередь.Это объясняет, почему вы видите, что время выполнения становится все больше и больше - каждый из ваших методов может выполняться примерно за одно и то же время, но они не запускаются до тех пор, пока не будут выполнены все предыдущие методы в очереди.
Чтобы отслеживать время, необходимое для фактического выполнения асинхронного метода, вы должны запускать и останавливать таймер в начале и конце вашего метода.
Вот документы для Пул потоков класс и статья о асинхронные методы это лучше объясняет, что происходит.
Асинхронные методы работают с использованием .NET ThreadPool
.Они перенесут работу на новый уровень. ThreadPool
поток (потенциально создающий его при необходимости, но обычно просто использующий повторно) для работы в фоновом режиме.
В вашем случае вы можете делать то, что делаете, однако осознайте, что ThreadPool
имеет ограниченное количество потоков, с которыми он будет работать.Вы собираетесь перенести свою работу в фоновые потоки, и первые будут запущены немедленно, но через некоторое время они встанут в очередь и не будут работать до тех пор, пока "задачи" не будут выполнены полностью.Это создаст впечатление, что нити становятся все длиннее и длиннее.
Однако ваши критерии секундомера несколько некорректны.Вы должны измерить общее время, необходимое для выполнения N задач, а не N раз для выполнения одной задачи.Это будет гораздо более полезная метрика.
Вполне возможно, что большая часть времени выполнения происходит раньше BeginMyMethod()
.В этом случае ваши измерения будут слишком низкими.На самом деле, в зависимости от API, BeginMyMethod()
может вызвать обратный вызов перед выходом из самого стека.Перемещение вызова вверх по StopWatch.Start()
тогда это должно помочь.