Асинхронный обратный вызов

36

Еще один способ для ожидания результатов от делегата заключается в применении так называемого асинхронного обратного вызова (asynchronous callback). С помощью третьего параметра в методе BeginInvoke можно передать метод, удовлетворяющий требованиям делегата AsyncCallback. Делегат AsyncCallback требует определять параметр IAsnycResult и возвращаемый тип void.

Вместо опроса делегата о том, завершился ли асинхронно вызванный метод, было бы более эффективно заставить вторичный поток информировать вызывающий поток о завершении выполнения задания. Чтобы включить такое поведение, необходимо передать экземпляр делегата System.AsyncCallback в качестве параметра методу BeginInvoke(); до сих пор этот параметр был равен null. Если передается объект AsyncCallback, делегат автоматически вызовет указанный метод по завершении асинхронного вызова.

Метод обратного вызова будет вызван во вторичном потоке, а не в первичном. Это имеет важное последствие для потоков с графическим интерфейсом пользователя (WPF или Windows Forms), поскольку элементы управления привязаны к потоку, который их создал, и могут управляться только им. Далее, при рассмотрении библиотеки TPL, будут показаны некоторые примеры работы потоков из графического интерфейса.

Как и любой делегат, AsyncCallback может вызывать только методы, соответствующие определенному шаблону, который в данном случае требует единственного параметра IAsyncResult и ничего не возвращает:

// Целевые методы AsyncCallback должны иметь следующую сигнатуру
void MyAsyncCallbackMethod(IAsyncResult res)

В рассматриваемом примере третьему параметру назначается адрес метода TakesAWhileCompleted, который удовлетворяет требованиям делегата AsyncCallback. В последнем параметре методу BeginInvoke можно передать объект, к которому будет производиться доступ из метода обратного вызова. Здесь удобно передать экземпляр самого делегата, так что метод обратного вызова сможет использовать его для получения результата асинхронного метода.

По завершении работы делегата DelegateThread сразу же вызывается метод TakesAWhileCompleted(). Ожидать результатов внутри главного потока нет никакой необходимости. Завершать главный поток перед завершением работы потоков делегатов может быть необязательно, если только остановка работы потоков делегатов при завершении выполнения главного потока не представляет проблемы:

using System.Diagnostics;
...                  
Console.WriteLine("\nАсинхронный вызов метода с двумя потоками: \n");
// Асинхронный вызов метода с применением делегата
BinaryOp bo = DelegateThread;

bo.BeginInvoke(1, 5000, TakesAWhileCompleted, bo);
for (int i = 0; i < 100; i++)
{
       // Выполнение операций в главном потоке
       Console.Write(".");
       Thread.Sleep(50);
}

Метод TakesAWhileCompleted() определяется с типами параметров и возврата, которые указаны в делегате AsyncCallback. Последний параметр, передаваемый в BeginInvoke(), может быть прочитан с помощью ar.AsyncState, а результат получен вызовом в TakesAWhileDelegate метода EndInvoke:

static void TakesAWhileCompleted(IAsyncResult ar)
        {
            if (ar == null) throw new ArgumentNullException("ar");

            BinaryOp bo = ar.AsyncState as BinaryOp;
            Trace.Assert(bo != null, "Неверный тип объекта");
            int result = bo.EndInvoke(ar);
            Console.WriteLine("Результат: " + result);
        }

Вместо того чтобы определять отдельный метод и передавать его методу BeginInvoke(), можно воспользоваться лямбда-выражением. Параметр ar имеет тип IAsyncResult. В такой реализации нет необходимости присваивать значение последнему параметру метода BeginInvoke(), потому что лямбда-выражение может напрямую получать доступ к переменной bo, находящейся за пределами контекста данного метода. Однако блок реализации лямбда-выражения все равно должен вызываться из потока делегата, что может и не быть очевидным при определении метода подобным образом.

Модель программирования и приемы, описанные для асинхронных делегатов — опрос, дескрипторы ожидания и асинхронные вызовы — доступны не только для делегатов. Эта же модель программирования, которая называется шаблоном асинхронного вызова (asynchronous pattern), встречается в разнообразных местах .NET Framework. Например, с помощью метода BeginGetResponse() класса HttpWebRequest можно асинхронно отправлять HTTP-запросы, а с помощью метода BeginExecuteReader() класса SqlCommand — запросы к базе данных. Параметры похожи на те, что можно передавать в методе BeginInvoke() делегата, а механизмы, применяемые для получения результатов, выглядят точно так же.

Пройди тесты
Лучший чат для C# программистов