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

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

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

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

Метод 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(3000);

}

Модифицируем предыдущий пример. Будем передавать в 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() основной поток выполнения приостанавливается до завершения работы соответствующего асинхронного метода.

Используем метод 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);

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