Асинхронные делегаты

В первой части было описано, как передать данные в поток, используя ParameterizedThreadStart. Иногда же нужно, наоборот, вернуть данные из потока, когда он завершит выполнение. Асинхронные делегаты предлагают для этого удобный механизм, позволяя передавать в обоих направлениях любое количество параметров с контролем их типа. Кроме того, необработанные исключения из асинхронных делегатов удобно перебрасываются в исходный поток и, таким образом, не требуют отдельной обработки. Асинхронные делегаты также предоставляют альтернативный путь к пулу потоков.

Цена, которую нужно заплатить – асинхронная модель. Чтобы понять, что это значит, сначала рассмотрим обычную, синхронную модель программирования. Скажем, нужно сравнить две web-страницы. Можно последовательно загрузить каждую страницу, а затем сравнить их примерно так:

static void ComparePages() { WebClient wc = new WebClient(); string s1 = wc.DownloadString("http://www.rsdn.ru"); string s2 = wc.DownloadString("http://rsdn.ru"); Console.WriteLine(s1 == s2? "Одинаковые": "Различные"); }

Конечно, было бы быстрее, если бы обе страницы загружались одновременно. Возможный взгляд на эту проблему – возложить вину на DownloadString, которая блокирует вызывающий метод на время загрузки страницы. Было бы хорошо вызвать DownloadString не блокирующим, асинхронным способом, другими словами:

1. Говорим DownloadString начать выполнение.

2. Исполняем другие задачи, пока она работает, например загружаем вторую страницу

3. Запрашиваем у DownloadString результаты.

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

Третий шаг – это то, что делает асинхронные делегаты полезными. Вызывающий поток стыкуется с рабочим, чтобы получить результаты и чтобы позволить повторную генерацию исключений. Без этого шага мы имеем нормальную многопоточность. Однако использование асинхронных делегатов без этого заключительного рандеву немногим более полезно, чем ThreadPool.QueueWorkerItem или BackgroundWorker.

Вот как можно использовать асинхронные делегаты для загрузки двух web-страниц, с одновременным выполнением других вычислений:

delegate string DownloadString(string uri); static void ComparePages() { // создаем два экземпляра делегата DownloadString: DownloadString download1 = new WebClient().DownloadString; DownloadString download2 = new WebClient().DownloadString; // Стартуем загрузку: IAsyncResult cookie1 = download1.BeginInvoke(uri1, null, null); IAsyncResult cookie2 = download2.BeginInvoke(uri2, null, null); // Выполняем какие-то вычисления: double seed = 1.23; for (int i = 0; i < 1000000; i++) seed = Math.Sqrt(seed + 1000); // Получаем результат загрузки, ожидая в случае необходимости. // Если были исключения, они будут сгенерированы здесь: string s1 = download1.EndInvoke(cookie1); string s2 = download2.EndInvoke(cookie2); Console.WriteLine(s1 == s2? "Одинаковые": "Различные"); }

Мы начинаем с объявления и создания делегатов для методов, которые хотим исполнить асинхронно. В этом примере нам нужны два делегата – каждый для отдельного объекта WebClient (WebClient не допускает параллельного доступа, если бы это было возможно, мы использовали бы один единственный делегат).

Далее вызываем BeginInvoke. Методы WebClient начинают исполняться, а управление немедленно возвращается в вызывающий код. В соответствии с сигнатурой делегата в BeginInvoke передается строка, а EndInvoke возвращает строку.

BeginInvoke нужны два дополнительных параметра – метод обратного вызова и объект с данными; обычно в них нет необходимости и можно передать в них null. BeginInvoke возвращает объект IASynchResult, используемый как cookie для вызова EndInvoke. У объекта IASynchResult есть также свойство IsCompleted, которое можно использовать для проверки завершения операции.

Далее мы вызываем для делегатов EndInvoke, так нам нужны их результаты. При необходимости EndInvoke будет ожидать завершения операции, а затем вернет значение, указанное в типе делегата (в нашем случае строку). Удобная особенность EndInvoke – если бы в DownloadString были ref- или out -параметры, они были бы добавлены в сигнатуру EndInvoke, позволяя возвратить таким образом несколько значений.

Если при исполнении асинхронного метода возникает необработанное исключение, оно будет повторно сгенерировано при вызове EndInvoke. Это позволяет аккуратно передавать исключения вызывающему коду.

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

Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: