Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Базовые технологии платформы .NET.docx
Скачиваний:
13
Добавлен:
03.11.2018
Размер:
614.46 Кб
Скачать

30. Асинхронный вызов методов

Платформа .NET содержит средства для поддержки асинхронного вызова методов. При асинхронном вызове поток выполнения разделяется на две части: в одной выполняется метод, а в другой – процесс программы. Асинхронный вызов служит альтернативой использованию многопоточности явным образом.

Асинхронный вызов всегда выполняется посредством объекта некоторого делегата. Любой такой объект содержит два специальных метода для асинхронных вызовов – BeginInvoke() и EndInvoke(). Эти методы генерируются во время выполнения программы, так как их сигнатура зависит от делегата1.

Метод BeginInvoke() обеспечивает асинхронный запуск. Кроме параметров, указанных при описании делегата, метод BeginInvoke() имеет два дополнительных параметра. Первый дополнительный параметр указывает на функцию завершения, выполняемую после окончания асинхронного метода. Второй дополнительный параметр – это объект, при помощи которого функции завершения может быть передана некоторая информация. Метод BeginInvoke() возвращает объект, реализующий интерфейс IAsyncResult. При помощи этого объекта становится возможным различать асинхронные вызовы одного и того же метода.

Приведём описание интерфейса IAsyncResult:

interface IAsyncResult

{

object AsyncState{ get; }

WaitHandle AsyncWaitHandle{ get; }

bool CompletedSynchronously{ get; }

bool IsCompleted{ get; }

}

Поле IsCompleted позволяет узнать, завершилась ли работа асинхронного метода. В поле AsyncWaitHandle хранится объект типа WaitHandle. Программист может вызывать методы объекта WaitHandle для контроля над потоком выполнения асинхронного метода. Объект AsyncState хранит последний аргумент, указанный при вызове BeginInvoke().

Делегат для функции завершения описан следующим образом:

public delegate void AsyncCallback(IAsyncResult ar);

Как видим, функции завершения передаётся единственный параметр ‑ объект, реализующий интерфейс IAsyncResult.

Рассмотрим пример асинхронного вызова метода, который вычисляет и печатает факториал целого числа. Ни функции завершения, ни возвращаемое BeginInvoke() значение не используется. Подобный подход при работе с асинхронными методами называется «выстрелил и забыл» (fire and forget).

// создадим лямбду, чтобы вычислять факторал

Func<uint, BigInteger> factorial = null;

factorial = n => (n == 0) ? 1 : n * factorial(n - 1);

// создадим лямбду, чтобы печатать факторал

Action<uint> print = n => Console.WriteLine(factorial(n));

// запустим метод асинхроно, игнорируя дополнительные параметры

print.BeginInvoke(8000, null, null);

// эмулируем работу (результат увидим где-то на третьей итерации)

for (int i = 1; i < 10; i++)

{

Console.Write("Do some work...");

Thread.Sleep(1000);

}

Модифицируем предыдущий пример. Будем передавать в BeginInvoke() функцию завершения и дополнительный параметр.

// объект print не изменился

// функция завершения будет выводить время выполнения метода

AsyncCallback timer = ar =>

{

var dt = (DateTime) ar.AsyncState;

Console.WriteLine(DateTime.Now - dt);

};

print.BeginInvoke(8000, timer, DateTime.Now);

print.BeginInvoke(1000, timer, DateTime.Now);

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

Изменим пример работы с асинхронными делегатами, превратив метод нахождения факториала в функцию:

// объект factorial определён в первом примере

// так как отслеживаем окончание работы методов,

// сохраняем результат вызова BeginInvoke()

IAsyncResult ar1 = factorial.BeginInvoke(8000, null, null);

IAsyncResult ar2 = factorial.BeginInvoke(1000, null, null);

Thread.Sleep(2000);

// получаем результат второго вызова и печатаем его

BigInteger res1 = factorial.EndInvoke(ar2);

Console.WriteLine(res1);

// получаем результат первого вызова

BigInteger res2 = factorial.EndInvoke(ar1);

Console.WriteLine(res2);

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