Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции OOP c#.doc
Скачиваний:
44
Добавлен:
22.09.2019
Размер:
3.38 Mб
Скачать

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

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

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

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

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

interface IAsyncResult {

object AsyncState{ get; }

WaitHandle AsyncWaitHandle{ get; }

bool CompletedSynchronously{ get; }

bool IsCompleted{ get; }

}

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

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

public delegate void AsyncCallback(IAsyncResult ar);

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

Рассмотрим пример, иллюстрирующий описанные возможности.

using System;

using System.Threading; // Нужно для "усыпления" потоков

// Делегат для асинхронного метода

public delegate void Func(int x);

class MainClass {

// Этот метод делает необходимую работу

public static void Fib(int n) {

int a = 1, b = 1, res = 1;

for(int i = 3; i <= n; i++) {

res = a + b;

a = b;

b = res;

Thread.Sleep(10); // Намерено замедлим!

}

Console.WriteLine("Fib calculated: " + res);

}

public static void Main() {

Func A = new Func(Fib);

// Асинхронный вызов "выстрелил и забыл"

// У BeginInvoke() три параметра, два не используем

A.BeginInvoke(6, null, null);

// Изображаем работу

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

Thread.Sleep(20);

Console.Write(i);

}

}

}

Вывод программы:

12Fib calculated: 8

3456789

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

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

using System;

using System.Threading;

public delegate void Func(int x);

class MainClass {

public static void Fib(int n) { . . . }

// Это будет функция завершения

public static void Callback(IAsyncResult ar) {

// Достаем параметр

string s = (string) ar.AsyncState;

Console.WriteLine("AsyncCall is finished with " + s);

}

public static void Main() {

Func A = new Func(Fib);

// Два асинхронных вызова

A.BeginInvoke(6, new AsyncCallback(Callback), "The end");

A.BeginInvoke(8, new AsyncCallback(Callback), "Second call");

// Изображаем работу

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

Thread.Sleep(20);

Console.Write(i);

}

}

}

Вывод программы:

12Fib calculated: 8

Async Call is finished with The end

345Fib calculated: 21

Async Call is finished with Second call

6789

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

Изменим метод Fib() из примера. Пусть он имеет следующую реализацию:

public static int Fib(int n, ref bool overflow) {

int a = 1, b = 1, res = 1;

overflow = false;

for (int i = 3; i <= n; i++) {

res = a + b;

// Устанавливаем флаг переполнения

if (res < 0) overflow = true;

a = b;

b = res;

}

return res;

}

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

using System;

using System.Threading;

// Вот такой у нас теперь делегат

public delegate int Func(int n, ref bool overflow);

class MainClass {

// Функция считает числа Фибоначчи, следя за переполнением

public static int Fib(int n, ref bool overflow) {

int a = 1, b = 1, res = 1;

overflow = false;

for(int i = 3; i <= n; i++) {

res = a + b;

// Устанавливаем флаг переполнения

if(res < 0) overflow = true;

a = b;

b = res;

}

return res;

}

public static void Main() {

bool over = false;

Func A = new Func(Fib);

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

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

IAsyncResult ar1 = A.BeginInvoke(10, ref over, null, null);

IAsyncResult ar2 = A.BeginInvoke(50, ref over, null, null);

// Имитируем бурную деятельность

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

Thread.Sleep(20);

Console.Write(i);

}

// Вспомнили про методы. Остановились, ждем результат

int res = A.EndInvoke(ref over, ar2);

Console.WriteLine("Result is {0}, overflowed = {1}",

res, over);

// Теперь второй метод

res = A.EndInvoke(ref over, ar1);

Console.WriteLine("Result is {0}, overflowed = {1}",

res, over);

}

}

Вывод программы:

123456789Result is -298632863, overflowed = True

Result is 55, overflowed = False

Подведем небольшой итог. Асинхронный вызов является альтернативой использования многопоточности, так как реализует ее неявно, при помощи среды исполнения. Широкий спектр настроек позволяет решать при помощи асинхронных вызовов большой круг практических задач программирования.