Асинхронный вызов метода - Особенности IAsyncResult

ОГЛАВЛЕНИЕ

Особенности IAsyncResult

Как EndInvoke отдает выходные параметры и обновленные параметры ref? Как EndInvoke выбрасывает исключение, выброшенное в функции? Допустим, был вызван BegineInvoke для Foo, а затем Foo закончила выполняться, и теперь обычно вызвался бы EndInvoke, но что если вызвать EndInvoke через 20 минут после завершения Foo? EndInvoke по-прежнему отдаст выходные параметры или параметры ref и выбросит то исключение (если оно было выброшено). Где хранится вся эта информация? Как EndInvoke получает все эти данные намного позже завершения функции? С помощью объекта IAsyncResult. Этот объект хранит всю информацию о вызове функции. EndInvoke принимает один параметр – объект типа IAsyncResult. Этот объект содержит следующую информацию:
•    Завершена ли функция?
•    Ссылка на делегат, используемый для BeginInvoke
•    Все выходные параметры и их значения
•    Все параметры ref и их измененные значения
•    Возвращаемое значение
•    Исключение, если оно было выброшено
•    И не только…

IAsyncResult может казаться безобидным, потому что является всего лишь интерфейсом для нескольких свойств, но на самом деле это объект типа System.Runtime.Remoting.Messaging.AsyncResult.

AsyncResult содержит объект по имени _replyMsg типа System.Runtime.Remoting.Messaging.ReturnMessage.

Пришлось уменьшить изображение выше, чтобы не прокручивать вправо для его чтения. Щелкните по изображению, чтобы просмотреть его
Четко видно возвращаемое значение, выходной параметр и параметры ref. Есть даже свойство «исключение» для хранения исключения. В окне отладки был развернут OutArgs, чтобы показать значение 200 и ссылку на вновь выделенный ArrayList. В свойстве ReturnValue видна строка “Спасибо за прочтение статьи”. Если бы имелось исключение, то EndInvoke выбросил бы его для перехвата. Это доказывает, что вся информация о вызове функции хранится в объекте IAsyncResult, возвращаемом из BeginInvoke, Он похож на ключ к данным. Если потерять этот объект, то вообще не удастся узнать выходные параметры, параметры ref и возвращаемое значение. Без этого объекта невозможно перехватить исключение. Это ключ, при потере которого информация навсегда теряется в дебрях среды выполнения .NET.

Использование делегата обратного вызова в стиле "не вызывайте меня, я вызову вас!"

Было объяснено, как передаются параметры, как передается состояние, и что метод выполняется в потоке в ThreadPool. Не было рассмотрено лишь уведомление о завершении выполнения метода. Блокирование и ожидание завершения метода не достигает многого. Чтобы получить уведомление, когда метод завершится, надо задать делегат обратного вызова в BeginInvoke. Посмотрите на следующие две функции:

private void CallFooWithOutAndRefParametersWithCallback()
{
    // создать параметры для передачи функции
    string strParam1 = "Param1";
    int intValue = 100;
    ArrayList list = new ArrayList();
    list.Add("Item1");

    // создать делегат
    DelegateWithOutAndRefParameters delFoo =
        new DelegateWithOutAndRefParameters(FooWithOutAndRefParameters);

    delFoo.BeginInvoke(strParam1,
        out intValue,
        ref list,
        new AsyncCallback(CallBack), // делегат обратного вызова
        null);
}

private void CallBack(IAsyncResult ar)
{
    // определить выходной параметр
    int intOutputValue;
    ArrayList list = null;

    // первый случай - IAsyncResult в объект AsyncResult, чтобы получить
    // делегат, использованный для вызова функции.
    AsyncResult result = (AsyncResult)ar;

    // захватить делегат
    DelegateWithOutAndRefParameters del =
        (DelegateWithOutAndRefParameters) result.AsyncDelegate;

    // теперь, когда есть делегат,
    // для него вызывается EndInvoke, чтобы получить всю
    // информацию о вызове метода.

    string strReturnValue = del.EndInvoke(out intOutputValue,
        ref list, ar);
}

Делегат был передан функции CallBack при вызове BeginInvoke. .NET вызовет, когда метод FooWithOutAndRefParameters закончит выполняться. Как и прежде, надо вызвать EndInvoke, чтобы получить выходные параметры. Чтобы вызвать EndInvoke, пришлось написать лишний код для получения делегата.

AsyncResult result = (AsyncResult)ar;
// захватить делегат
DelegateWithOutAndRefParameters del =
    (DelegateWithOutAndRefParameters) result.AsyncDelegate;

В каком потоке выполняется обратный вызов?

.NET вызывает обратный вызов с помощью делегата, то есть .NET вызывает этот делегат. Надо знать, в каком потоке выполняется код. Для получения четкого представления о происходящем в который раз была изменена функция Foo так, чтобы включила в себя информацию о потоке и добавила задержку в 4 секунды.

private string FooWithOutAndRefParameters(string param1,
        out int param2, ref ArrayList list)
{
    // вывести на экран информацию о потоке
    Trace.WriteLine("In FooWithOutAndRefParameters: Thread Pool? "
        + Thread.CurrentThread.IsThreadPoolThread.ToString() +
        " Thread Id: " + Thread.CurrentThread.GetHashCode());

    // ждать 4 секунды, словно эта функция долго выполняется.
    Thread.Sleep(4000);

    // изменить данные
    param1 = "Modify Value for param1";
    param2 = 200;
    list = new ArrayList();

    return "Thank you for reading this article";
}

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

private void CallBack(IAsyncResult ar)
{
    // в каком потоке находится?
    Trace.WriteLine("In Callback: Thread Pool? "
        + Thread.CurrentThread.IsThreadPoolThread.ToString() +
        " Thread Id: " + Thread.CurrentThread.GetHashCode());

    // определить выходной параметр
    int intOutputValue;
    ArrayList list = null;

    // первый случай - IAsyncResult в объект AsyncResult,
    // чтобы получить делегат, использованный для вызова функции.
    AsyncResult result = (AsyncResult)ar;

    // захватить делегат
    DelegateWithOutAndRefParameters del =
        (DelegateWithOutAndRefParameters) result.AsyncDelegate;

    // теперь, когда есть делегат, для него вызывается EndInvoke, чтобы
    // получить всю информацию о вызове метода.
    string strReturnValue = del.EndInvoke(out intOutputValue, ref list, ar);
}
FooWithOutAndRefParameters выполняется многократно с помощью кнопки на форме.
private void button4_Click(object sender, EventArgs e)
{
    CallFooWithOutAndRefParametersWithCallback();
}

Вывод после трехкратного нажатия кнопки (трехкратного вызова функции) следующий:

In FooWithOutAndRefParameters: Thread Pool? True Thread Id: 7
In FooWithOutAndRefParameters: Thread Pool? True Thread Id: 12
In FooWithOutAndRefParameters: Thread Pool? True Thread Id: 13
In Callback: Thread Pool? True Thread Id: 7
In Callback: Thread Pool? True Thread Id: 12
In Callback: Thread Pool? True Thread Id: 13

Функция Foo выполняется трижды, одна за другой, в трех разных потоках. Все потоки находятся в пуле потоков. Обратный вызов тоже выполняется трижды, соответственно, и все они тоже находятся в пуле потоков. Оказывается, что обратный вызов выполняется в том же идентификаторе потока, что и Foo. Поток 7 выполняет Foo; 4 секунды спустя обратный вызов тоже выполняется в потоке 7. То же самое свойственно потокам 12 и 13. Словно обратный вызов является продолжением функции Foo. Кнопку нажимали много раз, чтобы узнать, вызовется ли обратный вызов когда-нибудь в идентификаторе потока, отличном от того, в котором выполняется Foo. Этого не удалось добиться, что имеет смысл. Представьте, .NET захватил бы поток для вызова Foo и затем захватил бы другой поток для вызова обратного вызова, что было бы расточительством! При истощении пула потоков пришлось бы ждать свободного потока для вызова обратного вызова, что было бы кошмаром.

Читайте также:
  • Защита данных в .NET
    •    Примеры кода .NET по криптологии [исходник - 38.5 Кб] [демо - 9.96 Кб]•    Реализация AES и DES на C# [исходник - 35.7 Кб] [демо - 13.1 Кб] 1. Введение Криптология – область, занимающаяся обеспечением безопасности и конфиденциальности. Эта область включает в себя много криптосистем, кажда...
  • Формат файла .bin Office 2007
    •    Скачать чтение/запись OLE (C++) - 9 Кб•    Скачать считыватель BIFF12 (C++) - 404 Кб•    Скачать чтение/запись OLE (C#) - 10 Кб•    Скачать считыватель BIFF12 (C#) - 404 Кб Введение Новыми форматами файла Office 2007 являются файлы ZIP, содержащими части, некоторые из которых являются XML, ...
  • Упрощенный распределенный кэш
    Ища в поисковой машине Google или читая рекламу на технических сайтах или в журналах, можно легко найти много изощренных решений для кэширования. Например: MemCached, NCache, ScaleOut StateServer,Shared Cache(совместная кэш-память) и даже реализация Microsoft под названием Velocity(скорость). Ин...
  • IronPython – конфигурационный язык
    Требовалось создать конечный автомат для действующего продукта. Продукт был инструментом управления проектами и поэтому имел принцип задания, основанный на переходах действий пользователей из одного состояния в другое. Также требовалось, чтобы конечный автомат можно было настраивать по/для разных ...
  • Написание заданий ETL на чистом C#
    •    Скачать исходники - 3.57 Мб Введение Если вы не любите писать на C# и/или не против создания заданий ETL посредством конструктора и считаете, что конструктор включает в себя все нужные вам сценарии, то эта статья не для вас. Пример процесса в данной статье имеет 4 операции. Две – для чтени...