Как асинхронные методы работают в C #? - PullRequest
7 голосов
/ 24 октября 2009

Я использую асинхронные методы в некоторых проектах, и мне это нравится, потому что это позволяет моему приложению быть более масштабируемым. Однако мне интересно, как асинхронные методы действительно работают в фоновом режиме? Как .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?

Ответы [ 4 ]

3 голосов
/ 24 октября 2009

Код - это железная дорога, а нить - поезд. Когда поезд идет по железной дороге, он выполняет код.

BeginMyMethod выполняется основным потоком. Если вы заглянете внутрь BeginMyMethod, он просто добавляет делегата MyMethod в очередь ThreadPool. Фактическое MyMethod выполняется одним из поездов из состава поездов. Подпрограмма завершения, которая вызывается, когда MyMethod выполнена, выполняется тем же потоком, который выполнил MyMethod, а не вашим основным потоком, который выполняет остальную часть кода. Пока поток пула потоков занят выполнением MyMethod, основной поток может либо перейти на другую часть железнодорожной системы (выполнить другой код), либо просто поспать, ожидая, пока не загорится определенный семафор.

Поэтому нет такой вещи, как IAsyncResult «знать», когда вызывать подпрограмму завершения, вместо этого подпрограмма завершения - это просто делегат, вызываемый потоком пула потоков сразу после того, как он завершил выполнение MyMethod.

Надеюсь, вы не возражаете против несколько детской аналогии с поездами, я знаю, что это помогло мне не раз, когда я объяснял многопоточность людям.

1 голос
/ 24 октября 2009

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

В вашем случае вы можете делать то, что делаете, однако понимаете, что ThreadPool имеет ограниченное количество потоков, с которыми он будет работать. Вы собираетесь создавать свою работу в фоновых потоках, и первые будут выполняться немедленно, но через некоторое время они будут стоять в очереди, и не будут работать до тех пор, пока «задачи» не будут выполнены раньше, чем полностью. Это придаст вид нитей, занимающих все больше и больше времени.

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

1 голос
/ 24 октября 2009

Суть в том, что вызов Begin ставит в очередь запрос на выполнение вашего метода. Метод фактически выполняется в ThreadPool, который представляет собой набор рабочих потоков, предоставляемых средой выполнения.

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

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

Вот документы для класса ThreadPool и статья о асинхронных методах , которые лучше объясняют, что происходит.

0 голосов
/ 24 октября 2009

Возможно, что большинство времени выполнения происходит до BeginMyMethod(). В этом случае ваше измерение будет слишком низким. Фактически, в зависимости от API, BeginMyMethod() может вызвать обратный вызов перед тем, как покинуть стек. В этом случае должно помочь перемещение вверх по номеру StopWatch.Start().

...