Опять решила тряхнуть стариной, тем более тема делегатов всегда актуальна
и это, конечно, больше для себя
Много буковокДелегаты .NET: вызов асинхронных методов в среде .NET.
Одной из примечательных особенностей .NET Framework является встроенная асинхронная инфраструктура. В .NET вы можете асинхронно вызвать любой метод, определив для этого метода делегат и вызывая асинхронно методы этого делегата. Это является плюсом для вашего приложения, потому что, при синхронном вызове, происходит блокировка вызывающего потока до тех пор пока метод не будет завершен, в то время как асинхронный вызов производится в другом потоке, это позволяет первичному потоку продолжать свою работу, пока работает асинхронный вызов. В этой статье рассказывается о делегатах .NET и их использовании для асинхронных вызовов, обращая внимание на широко распространенные проблемы работы с потоками.
При разработке под Win32 большинство программистов используют синхронные вызовы API: поток начинает какую-то работу, затем терпеливо ждет когда она будет закончена. Если код уж совсем изощренный, то он может создать рабочий поток для этого синхронного вызова, освобождая главный поток для продолжения работы. Использование рабочих потоков для выполнения длительных блокирующих вызовов крайне важно для GUI приложений, потому что блокирование потока, который ставит сообщения в очередь тормозит весь интерфейс приложения.
Создание подобных рабочих потоков никогда не приведет к нужному результату. Создание нового рабочего потока каждый раз, когда вам необходимо сделать блокирующий вызов увеличивает число используемых потоков, потребляя ресурсы. В добавок, если вы думаете о передаче параметров в рабочий поток, и получении от него результатов, вам надо позаботиться о синхронизации потока. Так же вам надо будет подумать о том, как вызывающий поток будет уведомлен, о том что рабочий поток завершен. А это не тривиально.
Асинхронные вызовы в Microsoft .NET обеспечивают инфраструктуру для ответов на многие из этих вопросов. В этой статье я опишу как использовать асинхронные вызовы и укажу на некоторые моменты с которыми вы столкнетесь при их использовании.
АСИНХРОННОСТЬ
При асинхронном вызове метода, вызывающий поток блокируется пока вызов активен. Когда вызов завершается, метод может вернуть результаты по средством возвращаемого значения или любого из выходных параметров метода. Так как вызов асинхронный, вызывающий код гарантирует что вызов завершен а также что возвращенные значения допустимы(конечно, если метод без ошибок).
Когда поток выполняет асинхронный вызов метода, вызов возвращается немедленно. Вызывающий поток не блокируется; и может выполнять другие задачи. Инфраструктура получает другой поток для инвоука и передачи параметров из вызывающего кода. Таким образом асинхронный поток выполняется параллельно с вызвавшим его потоком. Если метод генерирует некоторые данные и возвращает это значение вызывающий метод должен иметь возможность доступ к этим данным. Асинхронная инфраструктура .NET поддерживает два механизма: вызывающий поток может или запросить результаты или сама инфраструктура может вернуть результаты вызывающему потоку когда они будут готовы.
COM в 32-битных системах может симулировать асинхронные вызовы: многопоточный сервер может создавать новые потоки для обработки запросов и возвращать результат немедленно. Чтобы вызывающему коду получить результаты, должен или существовать дополнительный(блокирующий) метод в вызываемом интерфейсе, который возвращает эти данные; или клиентский код должен реализовывать объект канала являющийся, обратный вызов которого возвращает результаты. Это показано на рис.1

Рис 1. Асинхронный вызов с использованием COM.
Проблема с первым механизмом в том, что другой вызов должен быть сделан к объекту для получния результатов, а если этот объект удаленный надо делать вызов по сети. Проблема с механизмом обратного вызова в том, что серверному объекту необходимо хранить ссылку на объект канала(sink object) до тех пор пока вызов не выполнится, что может привести к циклическим ссылкам. Короче, такая реализация асинхронных вызовов к COM приводит к многопоточному программированию, а это все сопутствующие сложности, с которыми вы сталкиваетесь при синхронизации и маршалинге потоков.
Windows2000 предлагает два других способа для осуществления асинхронных вызовов: COM+(слабо связанные) события и асинхронные интерфейсы COM. Подробно о событиях COM+ в "The COM+ Event Service Eases the Pain of Publishing and Subscribing to Data", про асинхронные интерфейсы COM "Marshaling Your Data: Efficient Data Transfer Techniques Using COM and Windows 2000,"
События COM+ доступны для программирования в стиле «вызвал-и-забыл», тут сервер выполняет обратные вызовы и COM+ событие системы определяет какому коду нужно данное событие, и при необходимости, выполняет этот код перед передачей событию. События COM+ это часть компонентных служб, и так как компонентные службы COM+ это часть компонентных служб .NET вы можете использовать стиль программирования .NET. Однако, сейчас не об этом.
Асинхронные интерфейсы COM использовали технику маршалинга, чтобы вы могли и вызывать асинхронно интерфейс, и асинхронно реализовывать метод. Важная часть это склейка между клиентским кодом и объектом – слой маршалинга – которая означает , что клиент имеет возможность вызывать объект синхронно или асинхронно, независимо от реализации интрефейса. Это так же означает, что интерфейс может быть реализован для асинхронных и синхронных вызовов независимо от того как вызывает их клиент. Единственным условием является то, что объекты должны быть запущены под Windows 2000 ( а не под управлением COM+) а интерфейсы маршализуются прокси-заглушками DLL-ек.
ДЕЛЕГАТЫ
Прежде чем рассказать об асинхронных вызовах, необходимо представление о делегатах. Делегат это безопасный указатель на функцию. Указатель на функцию в С, который принимает строку и возвращает size_t который может быть определен следующим образом:
typedef size_t(*FUNC)(const char*);
Это означает, что я могу создать указатель и присвоить адрес функции:
viod printSize(const char*)
{
FUNC f = strlen;
print(“%s is %ld chars\n”, str, f(str));
}
Проблема с сишными указателями состоит в том, что вы можете заставить их указывать на что угодно:
void throwException (const char* str)
{
FUNC f = (FUNC)strcat;
f(str); //bye bye
}
Без приведения, компилятор проверит, что тип указателя на функцию и функция которая ему присваивается совпадают. Но кастования тоже полезны, особенно когда указатель на функцию возвращается функцией API общего назначения типа GetProcAddress. В овремя выполнения нет проверки, что вызываемая фнкция имеет верный тип.
Одно из преимуществ .NET это контроль типов (type-safe), и предотвращение таких простых, но распространненых ошибок. Все типы в .NET самоописываемые, и делегаты и методы которые они вызывают все самоописываемое. То есть можно считать делегат самоописываем указателем на функцию. Делегат представляющий метод принимающий строку и возвращающий целое число выглядит так:
public delegate int StringDelegate(string str);
Можно объявить его в классе или в глобальном контексте. Компилятор C# создаст новый класс унаследованный от System.MulticastDelegate. На рис.2 показаны методы этого класса и его базовый класс System.Delegate. Это означает, что можно вызывать методы MulticastDelegate у любого делегата:
public void PassMeADelegate(StringDelegate d)
{
Console.WriteLine(“I was passed a delegate to a {0} method”, d.Method.ToString() );
} //
Очевидно, что это так же означает, что вы можете передавать ссылки на Делегат, потому что разрешено неявное преведение к базовому классу ссылок. Однако, при кастовании к базовому классу, метаданные объекта делегата сохраняются.
public void CastAway(Delegate d)
{
StringDelegate del = (StringDelegate)d;
}
В этом коде какстование скомпилируется, но выполнится в рантайме только если делегат d действительно типа StringDelegate, иначе исключение System.InvalidCastException.
Классы делегатов .NET ведут себя как контейнеры. Методы Combine и Remove используются для добавления и удаления информации о методах из этого контейнера, а GetInvocationList возвращает массив делегатов в контейнере. Как можно предположить из названия, Delegate содержит данные об одном единственном методе, тогда как MulticastDelegate содержит информацию более чем об одном методе. По сути вы не добавляете информцию о методе. Вместо этого вы добавляете два уже инициализированных делегата вместе через статический метод Combine, создавая новый объект делегат который содержит информацию содержащуюся в двух других.
ИСПОЛЬЗОВАНИЕ ДЕЛЕГАТОВ
На рис.3 приведен результат работы дизасссемблера языка MSIL , ILDASM, для языка С# для делегата описанного ранее. Класс sealed и ILDASM показывает, что эти методы пустые – это потому что .NET создаст реализацию в рантайме. Собственно говоря, компилятор C# только позволит вам генерировать классы делегатов с использованием ключевого слова delegate. Если вы попытаетесь унаследовать класс от System.MulticastDelegate, компилятор сгенерит ошибку CS0644, о том, что нельзя наследоваться от специального класса System.MulticastDelegate.
Чтобы использовать этот делегат, вам нужен метод с такой же сигнатурой – т.е. с таким же количеством и типом параметров и возвращающий такой же тип. Имя не обязательно должно совпадать, метод может быть статическим или экземплярным, и даже приватным. Делегаты должны быть инициализированы в их конструкторах методом который они будут вызывать:
Class MyCRT
{
public int strlen(string s)
{ return s.Length;}
}
MyCRT a = new MyCRT();
StringDelegate d =new StringDelegate(a.strlen);
Тут я создал новый делегат и проиницилизирован методом MyCRT.strlen экземпляра класса MyCRT. Так как я не использовал круглые скобочки у метода (прим. после a.strlen ) компилятор C# понимает, что я обращаюсь к указателю на метод, а не пытаюсь вызвать метод. Если бы MyCRT.strlen был статическим, тогда я мог бы инициализировать делегат без экземпляра класса; я бы просто передал точное имя метода с указанием его класса.
Вызывать метод легко, все что нужно вызвать у объекта делегата Invoke. Однако C# позволяет это сделать еще легче позволяя вызывать делегат как если бы это был метод:
int i;
string str = “.NET delegates”
// same as I = d.Invoke(str)
i = d(str);
Только параметры и возвращаемое значение должны подходить, когда вы передаете метод делегату, как вы можете видеть на рис.4, делегат на самом деле ссылается на приватный метод.
При инвоуке мультикаст делегата, он выполнит вызовы всех объектов, методы которых были зарегистрированы в делегате.Чтобы заенейблить его, все эти делегаты должны быть скомбинированы вместе, чтобы вызов одного только Invoke инвоучил их все. В С# есть операторы += и -= для удобства: все что делают эти операторы это вызывают методы Combine и Remove с соответствующими параметрами, как показано ниже:
StringDelegate d;
public void AddADelegate(StringDelegate dd);
{
// same as d = (StingDelegate)Delegate.Combine(d, dd);
d += dd;
}
Только делегаты одного типа могут быть скомбинированы; если вы попытаетесь скомбинировать делегаты разных типов, получите System.ArgumentException.
В предыдущих примерах делегат StringDelegate возвращал значение. А если скомбинировать несколько делегатов что будет в возвращаемом значение? Ответ - это будет значение последнего вызванного делегата- т.е. последнего скомбинированного делегата. Все другие значения не считаются. Если хотите использовать мультикаст делегат, то смысла в возвращаемом значении немного. Если вашим делегатам не надо возвращать значения, тогда можно использовать возможности «вызвать-и-забыть», где вызов делегата служит только для информирования подписанных клиентов о том, что что-то произошло. Этот же стиль используют Windows Forms для передачи сообщений вашему коду; у делегата EventHandler нет возвращаемого значения.
ОБРАТНЫЕ ВЫЗОВЫ ДЕЛЕГАТОВ
Давайте посмотрим как использовать для обратных вызовов, и один способ асинхронных вызовов. Первое требование это позволить серверному компоненту передать работу другому потоку, так чтобы метод мог сразу же вернуть управление. Потоки .NET происходят от класса System.Threading.Thread; конструктор принимает делегат, который будет вызван как процедура потока, как показано на рис.5.
Но это, конечно, не все. Как передавать параметры в поток? Как получать результаты от потока? Что случится если сделать много вызовов StartBigCalc – каждый раз надо будет создавать новый поток? Чтобы потокбезопасно передать параметры, можно использовать отдельный объект для обертывания метода и параметров для рабочего элемента, как показано на рис.6.
Для того чтобы получить результаты, есть множество способов. Во-первых, можно вызвать объект или у класса CThreadClass может быть объект для сохранения в него результатов, но нужен какой-то сигнальный механизм для уведомления о том, что результаты доступны. Или ваш рабочий поток может уведомить клиентский код via делегат, как показано на рис.7.
Здесь объявлен делегат возвращающий результат. У конструктора рабочего потока есть параметр делегат, который указывает на метод в клиентском коде. Рабочий поток высчитывает значение используя входные параметры и возвращает значение вызывая делегат. Клиентский код мог бы выглядеть как на рис.8.
Объект СCalling создается в другом потоке, отличном от потока объекта CWorker2, который будет делать обратный вызов через делегат. В этом коде только у рабочего потока будет доступ на запись переменной m_i в классе DoCalc. Я проигнорирую все возможные проблемы синхронизации, у нас более важная задача: как DoCalc узнает, что m_i было присвоено значение? В качестве хака я использовал метод Sleep, чтобы позволить рабочему потоку выполнить свою работу прежде чем я получу m_i, но этого недостаточно. В .NET есть решение по-лучше, позже увидите.
Если предполагается, что класс CDoAsync3 будет принимать много запросов за короткий промежуток времени, а процедура потока длительная, то идея создавать новый поток для каждого запроса не очень хорошая. Изменение контекста потока это ресурсоемкая операция, а большое количество потоков приведет к многочисленным изменениям контекста. Выходом является применение пула потоков. У каждого процесса есть как минимум один пул потоков, который создает .NET. Все что вам нужно сделать это вызвать статический метод ThreadPool.QueueUserWorkItem и передать делегат WaitCallback. Кода поток становится доступен в пуле потоков, инвочится WaitCallback. Например:
public class CDoAsync4
{
Public void StartBigCalc(int i, InformMe d)
{
CWorker2 w = new CWorker2(int I, d);
ThreadPool.QueueUserWorkItem( new WaitCallback(w.DoBigCalc));
t.Start();непонятно
}
}
У делегата WaitCallback есть объект параметр, который можно использовать для передачи данных методу через перегруженную версию QueueUserWorkItem. Это особенно полезно, если метод вызываемый делегатом статический.
Из данного обзора потоков и делегатов вы понимаете, что с их помощью можно делать кучу вещей, но создание безопасного асинхронного кода все еще проблема. Давайте рассмотрим альтернативные варианты.
АСИНХРОННЫЕ ВЫЗОВЫ ДЕЛЕГАТОВ
Давайте вернемся к классу приведенному на рис. 3, который был сгенерирован компилятором C# для ключевого слова delegate. Здесь показаны два важных метода:
public virtual IAsyncResult BeginInvoke(string str, AsyncCallback asc, object stateobject);
public virtual int EndInvoke(IAsyncResult result);
Параметрами для BeginInvoke будут являться входные параметры делегата, а EndInvoke будет выполнен с выходными параметрами. В C# параметры помеченные out являются выходными параметрами, помеченные ключевым словом ref могут быть и входными и выходными, а параметры не помеченные ни как ref, ни как out это соответственно входные параметры.
BeginInvoke вызывает метод асинхронно, инфраструктура ставит метод в очередь пула потоков и создает объекты синхронизации необходимые для определения того, что выполнение метода закончено. Если BeginInvoke порождает исключение, вы понимаете, что асинхронный метод вызван не был. EndInvoke предназначен для получения результатов, и предоставления системе произвести освобождение ресурсов. Если метод делегата выбрасывает исключение, асинхронный поток убивается и исключение перевыбрасывается в вызывающем потоке, когда ваш код вызывает EndInvoke (тут есть одно исключение, его обсудим позже).
Значение, которое возвращает BeginInvoke объект AsyncResult по интерфейсу IAsyncResult(рис. 9). Метод BeginInvoke возвращает этот объект раньше, чем метод делегата действительно вызван асинхронной инфраструктурой. Причина этого в том, что этот объект больше чем просто доступ к результатам; он на самом деле дает доступ к удаленной инфраструктуре, которая используется для асинхронных вызовов.
.NET remoting состоит из комбинации объектов приемников сообщений, которые обрабатывают сообщения объектов. Вызовы методов обращаются к прокси-объектам и представляются в виде сообщений, которые передаются через слой remoting’a реальным объектам. Объекты приемников сообщений используются для передачи этих сообщений, и они очень важны для обработки изменений контекста, которые могут потребоваться. Может быть много разных приемников сообщений используемых при вызове, и вот для этого нужно свойство AsyncResult.NextSink : для получения следующего приемника в цепочке.
При асинхронном вызове через прокси, запрос метода передается как параметр SyncProcessMessage, и приемник сообщения производит над сообщением свои преобразования, прежде чем передать следующему приемнику в цепочке. Последний приемник в цепочке вызывает реальный метод объекта, и возвращаемое значение мы получаем значение возвращаемое SyncProcessMessage.
Асинхронные методы более сложны. Вызов сделанный к AsyncProcessMessage получает сообщение, которое является сообщением вызова и указывает на объект приемника сообщения, который вызывается когда создан ответ. Этот объект приемника сообщения, который возвращает BeginInvoke. AsyncProcessMessage возвращает интерфейс IMessageCtrl , единственный метод Cancel которого используется для отмены асинхронного вызова. И асинхронный делегат никак не может вызвать этот метод.
Вы можете получить информацию о вызове используя методы IAsyncResult. Вы можете проверить закончилось ли выполнении метода или проверив свойство IsCompleted, или вызвав WaitOne у свойства AsyncWaitHandle. Но, внимание, если вы используете перегруженную версию WaitOne без таймаута, этот вызов может блокировать текущий поток, пока этот метод не будет завершен.

Рис.10 Асинхронный вызов объекта.
Здесь представлен один шаблон асинхронного вызова метода: вызываем метод, а потом или опрашиваем или ждем до завершения(рис.10). Когда метод завершается, можно вызвать EndInvoke чтобы получить результаты метода. EndInvoke возвращает значение метода. Если у метода есть out или ref значения, у EndInvoke есть параметры и для этих значений метода. В начале кода на рис. 11 вызывается делегат и опрашивается завершение, а код в нижней части ожидает завершения.
Если поток который вызывает делегат завершится раньше, чем метод вызова, то будет выброшено исключение. Поэтому всегда необходимо убедиться, что такого не случится, и при необходимости блокировать поток с помощью метода AsyncWaitHandle.WaitOne как показано на рис.11. Если будут выброшены исключения в методе вызванном через делегат, они будут направлены обратно в поток, который вызывал асинхронный делегат.
И последний способ вызвать делегат асинхронно, это использовать параметр AsyncCallback в методе BeginInvoke. Это позволяет вызывающему коду обеспечить метод обратного вызова, который и вызовет асинхронный поток, когда метод отработает. Это эквивалент кода на рис.7 . У метода AsyncCallBack есть параметр IAsyncResult , который в этот раз действительно указывает на настоящий результат. Однако у вас должна быть ссылка на делегат чтобы вызвать EndInvoke , а чтобы сделать это вы можете прикастовать параметр IAsyncResult, чтобы получить объект AsyncResult. Свойство AsyncDelegate этого объекта это делегат, который был вызван(см. Рис. 12).
Конечно, метод использованный для инициализации делегата AsyncCallback вовсе не обязательно должен совершать действия с тем же объектом, что и код вызывающий делегат. В такой ситуации вам может понадобиться передать дополнительные данные обратному вызову. Для этого вы можете использовать последний параметр метода BeginInvoke. Это может быть любой объект, а получить к нему доступ в методе EndInvoke можно через свойство AsyncState у параметра IAsyncResult.
Если у вызываемого метода есть ref параметр, то у методов BeginInvoke и EndInvoke тоже будут ref параметры. Параметр по ссылке может быть in/out , однако, не дайте себя одурачить, потому что параметр будет обновлен асинхронно. А чтобы это произошло вы должны вызвать соответствующий метод EndInvoke ; нельзя полагать, что параметр который вы передаете изменится магическим образом.
Например, на рис.13 показан делегат у которого есть ссылочный параметр. Он используется для асинхронного вызова метода times2. Запуская метод я жду окончания вызова, вывожу на печать значение переменной которое я передавал в качестве ref параметра. Если бы метод был вызван синхронно, переменная бы обновилась новым значением. Но для асинхронных вызовов это не вариант. Первый вызов WriteLine напечатает 42, входное значение. Чтобы получить выходное необходимо вызвать EndInvoke.
Если параметры метода являются ссылочными типами и передаются как параметры по значению, тогда асинхронный метод сможет вызывать методы со ссылочными типами, которые будут менять свое состояние. Вы можете использовать это для возвращения значения из асинхронного метода. На рис.14 показан пример. Делегат GetData принимает в качестве входного параметра объект типа System.Array. Так как передается он по ссылке, метод может изменять значения в массиве. Так как метод не возвращает никакого значения вызвать EndInvoke нет необходимости; вызывающий код может получить доступ к значениям в массиве. Однако, так как теперь доступ к объекту есть из двух потоков, вам следует убедиться, что вы не пытаетесь получить доступ к этому разделяемому объекту, до тех пор пока асинхронный код не отработает.
Если делегат возвращает void, код который инвочит делегат не заботится о возвращаемом значении метода, который был вызван. Чтобы помочь структуре ремоутинга, надо пометить метод атрибутом System.Runtime.Remoting.Messaging.OneWayAttribute. Если метод помеченный таким атрибутом порождает исключение, система затихорит исключение, а не будет передавать его вызывающему потоку через EndInvoke.
ИСПОЛЬЗОВАНИЕ АСИНХРОННЫХ ВЫЗОВОВ
Что радует при асинхронных вызовах в .NET это возможность вызвать любой метод следующим образом. Все что вам необходимо сделать это определить соответствующий делегат и вызвать его через BeginInvoke и EndInvoke. Вам не надо думать о создании потоков, не надо думать о том, как передаются параметры и возвращаются результаты; все для вас сделает асинхронная инфраструктура.
Некоторые из классов .NET библиотек базовых классов специально спроектированы для асинхронного вызова без использования делегатов. Класс System.IO.Stream является базовым классом для некоторых из них, так что будем использовать его в качестве примера. Асинхронные методы System.Stream показаны здесь:
Public virtual IAsyncResult BeginRead(
Byte[] buffer, int offset, int count, AsyncCallback callback, object state);
Public virtual int EndRead(IAsyncResult asyncResult);
Public virtual IAsyncResult BeginWrite (
Byte[] buffer, int offset, int count, AsyncCallback callback, object state);
Public virtual void EndWrite (IAsyncResult asyncResult);
Эти методы принимают указатель на заданный вами массив байтов. Так как вы передаете ссылку, объект потока заполнит значениями объект, который живет в вашем коде. Вы должны помнить о замечаниях, которые я сделал раньше. Ваш вызывающий код должен получать доступ к этому объекту, только когда вы уверены, что асинхронный поток уже перестал его использовать, например убедиться, что IAsyncResult. IsCompleted возвращает true.
Если вы решили обращаться к данным из AsyncCallback, метод обратного вызова вызывается после завершения асинхронного чтения или записи. Функция обратного вызова вызывается асинхронным потоком, так что вам надо убедиться в том, что после того как поток вызывает BeginRead он больше не обращается к буферу. Обратному вызову понадобится доступ к буферу, который вы можете обеспечить через последний параметр BeginRead. Как говорилось раньше, к объекту состояния передаваемому через этот параметр можно получить доступ через IAsyncResult.AsyncState. Код на рис.15 асинхронно получает доступ к веб странице используя буфер из 1000 байт через класс NetworkStream.
ЗАКЛЮЧЕНИЕ
В .NET есть встроенный асинхронный код. Вам не надо писать специальный код для асинхронного вызова методов. Некоторые из базовых классов библиотек .NET спроектированы для асинхронных вызовов, путем вызова соответствующих методовBeginXxx и EndXxx этот класс гарантировано запустит метод на другом потоке. У классов которые асинхронных методов не имеют, могут быть вызваны асинхронно с помощью делегатов. Классы делегатов генерит компилятор C# а имплементятся они в рантайме. Среда исполнения убеждается, что есть отдельный поток, чтобы сделать вызов и предоставляет объект синхронизации, чтобы позволить вашему коду проверять, когда завершится вызов. Асинхронные потоки используют многопоточный код, примите это во внимание при передаче ссылок на общедоступные объекты.
¬¬Figure 2 .NET Delegate Classes
[serializable, ClassInterface(ClassInterfaceType.AutoDual)]
public abstract class Delegate : System.Object, ICloneable, ISerializable
{
protected Delegate(Type target, string method);
protected Delegate(object target, string method);
public MethodInfo Method {get;}
public object Target {get;}
public virtual object Clone();
public static Delegate Combine(Delegate[] delegates);
public static Delegate Combine(Delegate a, Delegate b);
protected virtual Delegate CombineImpl(Delegate d);
public static Delegate CreateDelegate(Type type, MethodInfo method);
public static Delegate CreateDelegate(
Type type, Type target, string method);
public static Delegate CreateDelegate(
Type type, object target, string method);
public object DynamicInvoke(object[] args);
protected virtual object DynamicInvokeImpl(object[] args);
public override bool Equals(object obj);
public override int GetHashCode();
public virtual Delegate[] GetInvocationList();
protected virtual MethodInfo GetMethodImpl();
public virtual void GetObjectData(
SerializationInfo info, StreamingContext context);
public static Delegate Remove(Delegate source, Delegate value);
protected virtual Delegate RemoveImpl(Delegate d);
public static bool operator ==(Delegate d1, Delegate d2);
public static bool operator !=(Delegate d1, Delegate d2);
}
[serializable]
public abstract class MulticastDelegate : Delegate
{
protected MulticastDelegate(Type target, string method);
protected MulticastDelegate(object target, string method);
protected override Delegate CombineImpl(Delegate follow);
protected override object DynamicInvokeImpl(object[] args);
public override bool Equals(object obj);
public override Delegate[] GetInvocationList();
protected override Delegate RemoveImpl(Delegate value);
public static new bool operator ==(
MulticastDelegate d1, MulticastDelegate d2);
public static new bool operator !=(
MulticastDelegate d1, MulticastDelegate d2);
}
Figure 3 Delegate Class Generated by the C# Compiler
public sealed class StringDelegate : System.MulticastDelegate
{
public StringDelegate (object obj, int method);
public virtual int Invoke(string str);
public virtual IAsyncResult BeginInvoke(string str,
AsyncCallback asc, object stateObject);
public virtual int EndInvoke(IAsyncResult result);
}
Figure 4 Delegate Referencing Private Method
class MyOtherCRT
{
private int strlen(string s)
{return s.Length;}
public StringDelegate GetDel()
{
return new StringDelegate (strlen);
}
}
MyOtherCRT b = new MyOtherCRT();
StringDelegate d = b.GetDel();
int i = d(".NET delegates");
Figure 5 CThreadClass
public class CThreadClass
{
public void StartBigCalc()
{
Thread t = new Thread(new ThreadStart(DoBigCalc));
t.Start();
}
public void DoBigCalc()
{
// do the calculation here
}
}
Figure 6 Using a Separate Object to Pass Data
public class CWorker
{
private int m_i;
public CWorker(int i){m_i = i;}
public void DoBigCalc()
{
// do the calculation here using m_i
}
}
public class CThreadClass2
{
public void StartBigCalc(int i)
{
CWorker w = new CWorker(i);
Thread t = new Thread(new ThreadStart(w.DoBigCalc));
t.Start();
}
}
Figure 7 Returning Data via a Delegate Call
public delegate void InformMe(int i);
public class CWorker2
{
private int m_i;
private InformMe d;
public CWorker2(int i, InformMe dd)
{m_i = i; d = dd;}
public void DoBigCalc()
{
d(CalcReturnVal(m_i));
}
protected int CalcReturnVal(int i);
}
public class CDoAsync3
{
public void StartBigCalc(int i, InformMe d)
{
CWorker2 w = new CWorker2(i, d);
Thread t = new Thread(new ThreadStart(w.DoBigCalc));
t.Start();
}
}
Figure 8 Calling a Method on a Thread
public class CCalling
{
private int m_i;
public void GiveMeAResult(int i)
{
m_i = i;
}
public int DoCalc(int i)
{
CDoAsync3 a = new CDoAsync3();
a.StartBigCalc(i, new InformMe(this.GiveMeAResult));
Thread.Sleep(1000); // hack
return m_i;
}
}
Figure 9 AsyncResult Object
public class AsyncResult : System.Object, IAsyncResult, IMessageSink
{
internal AsyncResult(Message m);
public virtual void SetMessageCtrl(IMessageCtrl mc);
public virtual IMessage GetReplyMessage();
public virtual object AsyncDelegate {get;}
public bool EndInvokeCalled {get; set;}
// IAsyncResult
public virtual bool IsCompleted {get;}
public virtual bool CompletedSynchronously {get;}
public virtual object AsyncState {get;}
public virtual WaitHandle AsyncWaitHandle {get;}
// IMessageSink
public virtual IMessageSink NextSink {get;}
public virtual IMessageCtrl AsyncProcessMessage(
IMessage msg, IMessageSink replySink);
public virtual IMessage SyncProcessMessage(IMessage msg);
}
Figure 11 Calling a Method Asynchronously
Polling for Completion
StringDelegate d = new StringDelegate (A.strlen);
IAsyncResult ar;
ar = d.BeginInvoke("Hello", null, null);
while(!ar.IsCompleted)
{
// do work
}
int i = d.EndInvoke(ar);
// use result
Waiting for Completion
StringDelegate d = new StringDelegate (A.strlen);
IAsyncResult ar;
ar = d.BeginInvoke("Hello", null, null);
// do work
ar.AsyncWaitHandle.WaitOne();
int i = d.EndInvoke(ar);
// use result
Figure 12 Calling a Delegate and Providing a Callback
public class CCaller
{
private void CallMe(IAsyncResult ar)
{
StringDelegate d
= (StringDelegate)((AsyncResult)ar).AsyncDelegate;
int i = d.EndInvoke(ar);
// use result
}
public void DoCall()
{
StringDelegate d = new StringDelegate (A.strlen);
d.BeginInvoke("Hello", new AsyncCallback(CallMe), null);
}
}
Figure 13 Delegates with Ref or Out Parameters
class CAsyncCalls
{
public delegate void times2(ref int i);
public void p(ref int i)
{
i *= 2;
}
public void run()
{
times2 d = new times2(p);
int i = 42;
IAsyncResult ar;
ar = d.BeginInvoke(ref i, null, null);
ar.AsyncWaitHandle.WaitOne();
Console.WriteLine("before EndInvoke {0}", i);
d.EndInvoke(ref i, ar);
Console.WriteLine("after EndInvoke {0}", i);
}
}
Figure 14 Returning Data Through an In Parameter
class App
{
delegate void GetData(byte[] b);
static void GetBuf(byte[] b)
{
for (byte x = 0; x < b.Length; x++)
b[x] = (byte)(x*x);
}
static void Main()
{
GetData d = new GetData(App.GetBuf);
byte[] b = new byte[10];
IAsyncResult ar;
ar = d.BeginInvoke(b, null, null);
ar.AsyncWaitHandle.WaitOne();
for (int x = 0; x < b.Length; x++)
Console.Write("{0} ", b[x]);
}
}
Figure 15 Accessing an HTTP Server Asynchronously
void DumpPage(string host, string page)
{
TcpClient c = new TcpClient(host, http);
String str= "GET " + page + " HTTP/1.0\n\n";
byte[] req = new byte[str.Length + 1];
// convert request to bytes
Encoding.ASCII.GetBytes(str, 0, str.Length, req, 0);
NetworkStream s = (NetworkStream)c.GetStream();
// make the request for the page
s.Write(req, 0, req.Length);
// ask for the data in 1000 byte chunks
byte[] buf = new byte[1000];
while (s.DataAvailable)
{
IAsyncResult asr;
asr = s.BeginRead(buf, 0, buf.Length, null, null);
// do some other work here...
asr.AsyncWaitHandle.WaitOne();
// get the results, read how many bytes were obtained
int count = s.EndRead(asr);
char[] ch = new char[count];
Encoding.ASCII.GetChars(buf, 0, count, ch, 0);
String resp = new String(ch);
Console.Write(resp);
}
}
А еще я подстриглась и теперь похожа наНину Наталью Варлей )


Много буковокДелегаты .NET: вызов асинхронных методов в среде .NET.
Одной из примечательных особенностей .NET Framework является встроенная асинхронная инфраструктура. В .NET вы можете асинхронно вызвать любой метод, определив для этого метода делегат и вызывая асинхронно методы этого делегата. Это является плюсом для вашего приложения, потому что, при синхронном вызове, происходит блокировка вызывающего потока до тех пор пока метод не будет завершен, в то время как асинхронный вызов производится в другом потоке, это позволяет первичному потоку продолжать свою работу, пока работает асинхронный вызов. В этой статье рассказывается о делегатах .NET и их использовании для асинхронных вызовов, обращая внимание на широко распространенные проблемы работы с потоками.
При разработке под Win32 большинство программистов используют синхронные вызовы API: поток начинает какую-то работу, затем терпеливо ждет когда она будет закончена. Если код уж совсем изощренный, то он может создать рабочий поток для этого синхронного вызова, освобождая главный поток для продолжения работы. Использование рабочих потоков для выполнения длительных блокирующих вызовов крайне важно для GUI приложений, потому что блокирование потока, который ставит сообщения в очередь тормозит весь интерфейс приложения.
Создание подобных рабочих потоков никогда не приведет к нужному результату. Создание нового рабочего потока каждый раз, когда вам необходимо сделать блокирующий вызов увеличивает число используемых потоков, потребляя ресурсы. В добавок, если вы думаете о передаче параметров в рабочий поток, и получении от него результатов, вам надо позаботиться о синхронизации потока. Так же вам надо будет подумать о том, как вызывающий поток будет уведомлен, о том что рабочий поток завершен. А это не тривиально.
Асинхронные вызовы в Microsoft .NET обеспечивают инфраструктуру для ответов на многие из этих вопросов. В этой статье я опишу как использовать асинхронные вызовы и укажу на некоторые моменты с которыми вы столкнетесь при их использовании.
АСИНХРОННОСТЬ
При асинхронном вызове метода, вызывающий поток блокируется пока вызов активен. Когда вызов завершается, метод может вернуть результаты по средством возвращаемого значения или любого из выходных параметров метода. Так как вызов асинхронный, вызывающий код гарантирует что вызов завершен а также что возвращенные значения допустимы(конечно, если метод без ошибок).
Когда поток выполняет асинхронный вызов метода, вызов возвращается немедленно. Вызывающий поток не блокируется; и может выполнять другие задачи. Инфраструктура получает другой поток для инвоука и передачи параметров из вызывающего кода. Таким образом асинхронный поток выполняется параллельно с вызвавшим его потоком. Если метод генерирует некоторые данные и возвращает это значение вызывающий метод должен иметь возможность доступ к этим данным. Асинхронная инфраструктура .NET поддерживает два механизма: вызывающий поток может или запросить результаты или сама инфраструктура может вернуть результаты вызывающему потоку когда они будут готовы.
COM в 32-битных системах может симулировать асинхронные вызовы: многопоточный сервер может создавать новые потоки для обработки запросов и возвращать результат немедленно. Чтобы вызывающему коду получить результаты, должен или существовать дополнительный(блокирующий) метод в вызываемом интерфейсе, который возвращает эти данные; или клиентский код должен реализовывать объект канала являющийся, обратный вызов которого возвращает результаты. Это показано на рис.1

Рис 1. Асинхронный вызов с использованием COM.
Проблема с первым механизмом в том, что другой вызов должен быть сделан к объекту для получния результатов, а если этот объект удаленный надо делать вызов по сети. Проблема с механизмом обратного вызова в том, что серверному объекту необходимо хранить ссылку на объект канала(sink object) до тех пор пока вызов не выполнится, что может привести к циклическим ссылкам. Короче, такая реализация асинхронных вызовов к COM приводит к многопоточному программированию, а это все сопутствующие сложности, с которыми вы сталкиваетесь при синхронизации и маршалинге потоков.
Windows2000 предлагает два других способа для осуществления асинхронных вызовов: COM+(слабо связанные) события и асинхронные интерфейсы COM. Подробно о событиях COM+ в "The COM+ Event Service Eases the Pain of Publishing and Subscribing to Data", про асинхронные интерфейсы COM "Marshaling Your Data: Efficient Data Transfer Techniques Using COM and Windows 2000,"
События COM+ доступны для программирования в стиле «вызвал-и-забыл», тут сервер выполняет обратные вызовы и COM+ событие системы определяет какому коду нужно данное событие, и при необходимости, выполняет этот код перед передачей событию. События COM+ это часть компонентных служб, и так как компонентные службы COM+ это часть компонентных служб .NET вы можете использовать стиль программирования .NET. Однако, сейчас не об этом.
Асинхронные интерфейсы COM использовали технику маршалинга, чтобы вы могли и вызывать асинхронно интерфейс, и асинхронно реализовывать метод. Важная часть это склейка между клиентским кодом и объектом – слой маршалинга – которая означает , что клиент имеет возможность вызывать объект синхронно или асинхронно, независимо от реализации интрефейса. Это так же означает, что интерфейс может быть реализован для асинхронных и синхронных вызовов независимо от того как вызывает их клиент. Единственным условием является то, что объекты должны быть запущены под Windows 2000 ( а не под управлением COM+) а интерфейсы маршализуются прокси-заглушками DLL-ек.
ДЕЛЕГАТЫ
Прежде чем рассказать об асинхронных вызовах, необходимо представление о делегатах. Делегат это безопасный указатель на функцию. Указатель на функцию в С, который принимает строку и возвращает size_t который может быть определен следующим образом:
typedef size_t(*FUNC)(const char*);
Это означает, что я могу создать указатель и присвоить адрес функции:
viod printSize(const char*)
{
FUNC f = strlen;
print(“%s is %ld chars\n”, str, f(str));
}
Проблема с сишными указателями состоит в том, что вы можете заставить их указывать на что угодно:
void throwException (const char* str)
{
FUNC f = (FUNC)strcat;
f(str); //bye bye
}
Без приведения, компилятор проверит, что тип указателя на функцию и функция которая ему присваивается совпадают. Но кастования тоже полезны, особенно когда указатель на функцию возвращается функцией API общего назначения типа GetProcAddress. В овремя выполнения нет проверки, что вызываемая фнкция имеет верный тип.
Одно из преимуществ .NET это контроль типов (type-safe), и предотвращение таких простых, но распространненых ошибок. Все типы в .NET самоописываемые, и делегаты и методы которые они вызывают все самоописываемое. То есть можно считать делегат самоописываем указателем на функцию. Делегат представляющий метод принимающий строку и возвращающий целое число выглядит так:
public delegate int StringDelegate(string str);
Можно объявить его в классе или в глобальном контексте. Компилятор C# создаст новый класс унаследованный от System.MulticastDelegate. На рис.2 показаны методы этого класса и его базовый класс System.Delegate. Это означает, что можно вызывать методы MulticastDelegate у любого делегата:
public void PassMeADelegate(StringDelegate d)
{
Console.WriteLine(“I was passed a delegate to a {0} method”, d.Method.ToString() );
} //
Очевидно, что это так же означает, что вы можете передавать ссылки на Делегат, потому что разрешено неявное преведение к базовому классу ссылок. Однако, при кастовании к базовому классу, метаданные объекта делегата сохраняются.
public void CastAway(Delegate d)
{
StringDelegate del = (StringDelegate)d;
}
В этом коде какстование скомпилируется, но выполнится в рантайме только если делегат d действительно типа StringDelegate, иначе исключение System.InvalidCastException.
Классы делегатов .NET ведут себя как контейнеры. Методы Combine и Remove используются для добавления и удаления информации о методах из этого контейнера, а GetInvocationList возвращает массив делегатов в контейнере. Как можно предположить из названия, Delegate содержит данные об одном единственном методе, тогда как MulticastDelegate содержит информацию более чем об одном методе. По сути вы не добавляете информцию о методе. Вместо этого вы добавляете два уже инициализированных делегата вместе через статический метод Combine, создавая новый объект делегат который содержит информацию содержащуюся в двух других.
ИСПОЛЬЗОВАНИЕ ДЕЛЕГАТОВ
На рис.3 приведен результат работы дизасссемблера языка MSIL , ILDASM, для языка С# для делегата описанного ранее. Класс sealed и ILDASM показывает, что эти методы пустые – это потому что .NET создаст реализацию в рантайме. Собственно говоря, компилятор C# только позволит вам генерировать классы делегатов с использованием ключевого слова delegate. Если вы попытаетесь унаследовать класс от System.MulticastDelegate, компилятор сгенерит ошибку CS0644, о том, что нельзя наследоваться от специального класса System.MulticastDelegate.
Чтобы использовать этот делегат, вам нужен метод с такой же сигнатурой – т.е. с таким же количеством и типом параметров и возвращающий такой же тип. Имя не обязательно должно совпадать, метод может быть статическим или экземплярным, и даже приватным. Делегаты должны быть инициализированы в их конструкторах методом который они будут вызывать:
Class MyCRT
{
public int strlen(string s)
{ return s.Length;}
}
MyCRT a = new MyCRT();
StringDelegate d =new StringDelegate(a.strlen);
Тут я создал новый делегат и проиницилизирован методом MyCRT.strlen экземпляра класса MyCRT. Так как я не использовал круглые скобочки у метода (прим. после a.strlen ) компилятор C# понимает, что я обращаюсь к указателю на метод, а не пытаюсь вызвать метод. Если бы MyCRT.strlen был статическим, тогда я мог бы инициализировать делегат без экземпляра класса; я бы просто передал точное имя метода с указанием его класса.
Вызывать метод легко, все что нужно вызвать у объекта делегата Invoke. Однако C# позволяет это сделать еще легче позволяя вызывать делегат как если бы это был метод:
int i;
string str = “.NET delegates”
// same as I = d.Invoke(str)
i = d(str);
Только параметры и возвращаемое значение должны подходить, когда вы передаете метод делегату, как вы можете видеть на рис.4, делегат на самом деле ссылается на приватный метод.
При инвоуке мультикаст делегата, он выполнит вызовы всех объектов, методы которых были зарегистрированы в делегате.Чтобы заенейблить его, все эти делегаты должны быть скомбинированы вместе, чтобы вызов одного только Invoke инвоучил их все. В С# есть операторы += и -= для удобства: все что делают эти операторы это вызывают методы Combine и Remove с соответствующими параметрами, как показано ниже:
StringDelegate d;
public void AddADelegate(StringDelegate dd);
{
// same as d = (StingDelegate)Delegate.Combine(d, dd);
d += dd;
}
Только делегаты одного типа могут быть скомбинированы; если вы попытаетесь скомбинировать делегаты разных типов, получите System.ArgumentException.
В предыдущих примерах делегат StringDelegate возвращал значение. А если скомбинировать несколько делегатов что будет в возвращаемом значение? Ответ - это будет значение последнего вызванного делегата- т.е. последнего скомбинированного делегата. Все другие значения не считаются. Если хотите использовать мультикаст делегат, то смысла в возвращаемом значении немного. Если вашим делегатам не надо возвращать значения, тогда можно использовать возможности «вызвать-и-забыть», где вызов делегата служит только для информирования подписанных клиентов о том, что что-то произошло. Этот же стиль используют Windows Forms для передачи сообщений вашему коду; у делегата EventHandler нет возвращаемого значения.
ОБРАТНЫЕ ВЫЗОВЫ ДЕЛЕГАТОВ
Давайте посмотрим как использовать для обратных вызовов, и один способ асинхронных вызовов. Первое требование это позволить серверному компоненту передать работу другому потоку, так чтобы метод мог сразу же вернуть управление. Потоки .NET происходят от класса System.Threading.Thread; конструктор принимает делегат, который будет вызван как процедура потока, как показано на рис.5.
Но это, конечно, не все. Как передавать параметры в поток? Как получать результаты от потока? Что случится если сделать много вызовов StartBigCalc – каждый раз надо будет создавать новый поток? Чтобы потокбезопасно передать параметры, можно использовать отдельный объект для обертывания метода и параметров для рабочего элемента, как показано на рис.6.
Для того чтобы получить результаты, есть множество способов. Во-первых, можно вызвать объект или у класса CThreadClass может быть объект для сохранения в него результатов, но нужен какой-то сигнальный механизм для уведомления о том, что результаты доступны. Или ваш рабочий поток может уведомить клиентский код via делегат, как показано на рис.7.
Здесь объявлен делегат возвращающий результат. У конструктора рабочего потока есть параметр делегат, который указывает на метод в клиентском коде. Рабочий поток высчитывает значение используя входные параметры и возвращает значение вызывая делегат. Клиентский код мог бы выглядеть как на рис.8.
Объект СCalling создается в другом потоке, отличном от потока объекта CWorker2, который будет делать обратный вызов через делегат. В этом коде только у рабочего потока будет доступ на запись переменной m_i в классе DoCalc. Я проигнорирую все возможные проблемы синхронизации, у нас более важная задача: как DoCalc узнает, что m_i было присвоено значение? В качестве хака я использовал метод Sleep, чтобы позволить рабочему потоку выполнить свою работу прежде чем я получу m_i, но этого недостаточно. В .NET есть решение по-лучше, позже увидите.
Если предполагается, что класс CDoAsync3 будет принимать много запросов за короткий промежуток времени, а процедура потока длительная, то идея создавать новый поток для каждого запроса не очень хорошая. Изменение контекста потока это ресурсоемкая операция, а большое количество потоков приведет к многочисленным изменениям контекста. Выходом является применение пула потоков. У каждого процесса есть как минимум один пул потоков, который создает .NET. Все что вам нужно сделать это вызвать статический метод ThreadPool.QueueUserWorkItem и передать делегат WaitCallback. Кода поток становится доступен в пуле потоков, инвочится WaitCallback. Например:
public class CDoAsync4
{
Public void StartBigCalc(int i, InformMe d)
{
CWorker2 w = new CWorker2(int I, d);
ThreadPool.QueueUserWorkItem( new WaitCallback(w.DoBigCalc));
t.Start();непонятно
}
}
У делегата WaitCallback есть объект параметр, который можно использовать для передачи данных методу через перегруженную версию QueueUserWorkItem. Это особенно полезно, если метод вызываемый делегатом статический.
Из данного обзора потоков и делегатов вы понимаете, что с их помощью можно делать кучу вещей, но создание безопасного асинхронного кода все еще проблема. Давайте рассмотрим альтернативные варианты.
АСИНХРОННЫЕ ВЫЗОВЫ ДЕЛЕГАТОВ
Давайте вернемся к классу приведенному на рис. 3, который был сгенерирован компилятором C# для ключевого слова delegate. Здесь показаны два важных метода:
public virtual IAsyncResult BeginInvoke(string str, AsyncCallback asc, object stateobject);
public virtual int EndInvoke(IAsyncResult result);
Параметрами для BeginInvoke будут являться входные параметры делегата, а EndInvoke будет выполнен с выходными параметрами. В C# параметры помеченные out являются выходными параметрами, помеченные ключевым словом ref могут быть и входными и выходными, а параметры не помеченные ни как ref, ни как out это соответственно входные параметры.
BeginInvoke вызывает метод асинхронно, инфраструктура ставит метод в очередь пула потоков и создает объекты синхронизации необходимые для определения того, что выполнение метода закончено. Если BeginInvoke порождает исключение, вы понимаете, что асинхронный метод вызван не был. EndInvoke предназначен для получения результатов, и предоставления системе произвести освобождение ресурсов. Если метод делегата выбрасывает исключение, асинхронный поток убивается и исключение перевыбрасывается в вызывающем потоке, когда ваш код вызывает EndInvoke (тут есть одно исключение, его обсудим позже).
Значение, которое возвращает BeginInvoke объект AsyncResult по интерфейсу IAsyncResult(рис. 9). Метод BeginInvoke возвращает этот объект раньше, чем метод делегата действительно вызван асинхронной инфраструктурой. Причина этого в том, что этот объект больше чем просто доступ к результатам; он на самом деле дает доступ к удаленной инфраструктуре, которая используется для асинхронных вызовов.
.NET remoting состоит из комбинации объектов приемников сообщений, которые обрабатывают сообщения объектов. Вызовы методов обращаются к прокси-объектам и представляются в виде сообщений, которые передаются через слой remoting’a реальным объектам. Объекты приемников сообщений используются для передачи этих сообщений, и они очень важны для обработки изменений контекста, которые могут потребоваться. Может быть много разных приемников сообщений используемых при вызове, и вот для этого нужно свойство AsyncResult.NextSink : для получения следующего приемника в цепочке.
При асинхронном вызове через прокси, запрос метода передается как параметр SyncProcessMessage, и приемник сообщения производит над сообщением свои преобразования, прежде чем передать следующему приемнику в цепочке. Последний приемник в цепочке вызывает реальный метод объекта, и возвращаемое значение мы получаем значение возвращаемое SyncProcessMessage.
Асинхронные методы более сложны. Вызов сделанный к AsyncProcessMessage получает сообщение, которое является сообщением вызова и указывает на объект приемника сообщения, который вызывается когда создан ответ. Этот объект приемника сообщения, который возвращает BeginInvoke. AsyncProcessMessage возвращает интерфейс IMessageCtrl , единственный метод Cancel которого используется для отмены асинхронного вызова. И асинхронный делегат никак не может вызвать этот метод.
Вы можете получить информацию о вызове используя методы IAsyncResult. Вы можете проверить закончилось ли выполнении метода или проверив свойство IsCompleted, или вызвав WaitOne у свойства AsyncWaitHandle. Но, внимание, если вы используете перегруженную версию WaitOne без таймаута, этот вызов может блокировать текущий поток, пока этот метод не будет завершен.

Рис.10 Асинхронный вызов объекта.
Здесь представлен один шаблон асинхронного вызова метода: вызываем метод, а потом или опрашиваем или ждем до завершения(рис.10). Когда метод завершается, можно вызвать EndInvoke чтобы получить результаты метода. EndInvoke возвращает значение метода. Если у метода есть out или ref значения, у EndInvoke есть параметры и для этих значений метода. В начале кода на рис. 11 вызывается делегат и опрашивается завершение, а код в нижней части ожидает завершения.
Если поток который вызывает делегат завершится раньше, чем метод вызова, то будет выброшено исключение. Поэтому всегда необходимо убедиться, что такого не случится, и при необходимости блокировать поток с помощью метода AsyncWaitHandle.WaitOne как показано на рис.11. Если будут выброшены исключения в методе вызванном через делегат, они будут направлены обратно в поток, который вызывал асинхронный делегат.
И последний способ вызвать делегат асинхронно, это использовать параметр AsyncCallback в методе BeginInvoke. Это позволяет вызывающему коду обеспечить метод обратного вызова, который и вызовет асинхронный поток, когда метод отработает. Это эквивалент кода на рис.7 . У метода AsyncCallBack есть параметр IAsyncResult , который в этот раз действительно указывает на настоящий результат. Однако у вас должна быть ссылка на делегат чтобы вызвать EndInvoke , а чтобы сделать это вы можете прикастовать параметр IAsyncResult, чтобы получить объект AsyncResult. Свойство AsyncDelegate этого объекта это делегат, который был вызван(см. Рис. 12).
Конечно, метод использованный для инициализации делегата AsyncCallback вовсе не обязательно должен совершать действия с тем же объектом, что и код вызывающий делегат. В такой ситуации вам может понадобиться передать дополнительные данные обратному вызову. Для этого вы можете использовать последний параметр метода BeginInvoke. Это может быть любой объект, а получить к нему доступ в методе EndInvoke можно через свойство AsyncState у параметра IAsyncResult.
Если у вызываемого метода есть ref параметр, то у методов BeginInvoke и EndInvoke тоже будут ref параметры. Параметр по ссылке может быть in/out , однако, не дайте себя одурачить, потому что параметр будет обновлен асинхронно. А чтобы это произошло вы должны вызвать соответствующий метод EndInvoke ; нельзя полагать, что параметр который вы передаете изменится магическим образом.
Например, на рис.13 показан делегат у которого есть ссылочный параметр. Он используется для асинхронного вызова метода times2. Запуская метод я жду окончания вызова, вывожу на печать значение переменной которое я передавал в качестве ref параметра. Если бы метод был вызван синхронно, переменная бы обновилась новым значением. Но для асинхронных вызовов это не вариант. Первый вызов WriteLine напечатает 42, входное значение. Чтобы получить выходное необходимо вызвать EndInvoke.
Если параметры метода являются ссылочными типами и передаются как параметры по значению, тогда асинхронный метод сможет вызывать методы со ссылочными типами, которые будут менять свое состояние. Вы можете использовать это для возвращения значения из асинхронного метода. На рис.14 показан пример. Делегат GetData принимает в качестве входного параметра объект типа System.Array. Так как передается он по ссылке, метод может изменять значения в массиве. Так как метод не возвращает никакого значения вызвать EndInvoke нет необходимости; вызывающий код может получить доступ к значениям в массиве. Однако, так как теперь доступ к объекту есть из двух потоков, вам следует убедиться, что вы не пытаетесь получить доступ к этому разделяемому объекту, до тех пор пока асинхронный код не отработает.
Если делегат возвращает void, код который инвочит делегат не заботится о возвращаемом значении метода, который был вызван. Чтобы помочь структуре ремоутинга, надо пометить метод атрибутом System.Runtime.Remoting.Messaging.OneWayAttribute. Если метод помеченный таким атрибутом порождает исключение, система затихорит исключение, а не будет передавать его вызывающему потоку через EndInvoke.
ИСПОЛЬЗОВАНИЕ АСИНХРОННЫХ ВЫЗОВОВ
Что радует при асинхронных вызовах в .NET это возможность вызвать любой метод следующим образом. Все что вам необходимо сделать это определить соответствующий делегат и вызвать его через BeginInvoke и EndInvoke. Вам не надо думать о создании потоков, не надо думать о том, как передаются параметры и возвращаются результаты; все для вас сделает асинхронная инфраструктура.
Некоторые из классов .NET библиотек базовых классов специально спроектированы для асинхронного вызова без использования делегатов. Класс System.IO.Stream является базовым классом для некоторых из них, так что будем использовать его в качестве примера. Асинхронные методы System.Stream показаны здесь:
Public virtual IAsyncResult BeginRead(
Byte[] buffer, int offset, int count, AsyncCallback callback, object state);
Public virtual int EndRead(IAsyncResult asyncResult);
Public virtual IAsyncResult BeginWrite (
Byte[] buffer, int offset, int count, AsyncCallback callback, object state);
Public virtual void EndWrite (IAsyncResult asyncResult);
Эти методы принимают указатель на заданный вами массив байтов. Так как вы передаете ссылку, объект потока заполнит значениями объект, который живет в вашем коде. Вы должны помнить о замечаниях, которые я сделал раньше. Ваш вызывающий код должен получать доступ к этому объекту, только когда вы уверены, что асинхронный поток уже перестал его использовать, например убедиться, что IAsyncResult. IsCompleted возвращает true.
Если вы решили обращаться к данным из AsyncCallback, метод обратного вызова вызывается после завершения асинхронного чтения или записи. Функция обратного вызова вызывается асинхронным потоком, так что вам надо убедиться в том, что после того как поток вызывает BeginRead он больше не обращается к буферу. Обратному вызову понадобится доступ к буферу, который вы можете обеспечить через последний параметр BeginRead. Как говорилось раньше, к объекту состояния передаваемому через этот параметр можно получить доступ через IAsyncResult.AsyncState. Код на рис.15 асинхронно получает доступ к веб странице используя буфер из 1000 байт через класс NetworkStream.
ЗАКЛЮЧЕНИЕ
В .NET есть встроенный асинхронный код. Вам не надо писать специальный код для асинхронного вызова методов. Некоторые из базовых классов библиотек .NET спроектированы для асинхронных вызовов, путем вызова соответствующих методовBeginXxx и EndXxx этот класс гарантировано запустит метод на другом потоке. У классов которые асинхронных методов не имеют, могут быть вызваны асинхронно с помощью делегатов. Классы делегатов генерит компилятор C# а имплементятся они в рантайме. Среда исполнения убеждается, что есть отдельный поток, чтобы сделать вызов и предоставляет объект синхронизации, чтобы позволить вашему коду проверять, когда завершится вызов. Асинхронные потоки используют многопоточный код, примите это во внимание при передаче ссылок на общедоступные объекты.
¬¬Figure 2 .NET Delegate Classes
[serializable, ClassInterface(ClassInterfaceType.AutoDual)]
public abstract class Delegate : System.Object, ICloneable, ISerializable
{
protected Delegate(Type target, string method);
protected Delegate(object target, string method);
public MethodInfo Method {get;}
public object Target {get;}
public virtual object Clone();
public static Delegate Combine(Delegate[] delegates);
public static Delegate Combine(Delegate a, Delegate b);
protected virtual Delegate CombineImpl(Delegate d);
public static Delegate CreateDelegate(Type type, MethodInfo method);
public static Delegate CreateDelegate(
Type type, Type target, string method);
public static Delegate CreateDelegate(
Type type, object target, string method);
public object DynamicInvoke(object[] args);
protected virtual object DynamicInvokeImpl(object[] args);
public override bool Equals(object obj);
public override int GetHashCode();
public virtual Delegate[] GetInvocationList();
protected virtual MethodInfo GetMethodImpl();
public virtual void GetObjectData(
SerializationInfo info, StreamingContext context);
public static Delegate Remove(Delegate source, Delegate value);
protected virtual Delegate RemoveImpl(Delegate d);
public static bool operator ==(Delegate d1, Delegate d2);
public static bool operator !=(Delegate d1, Delegate d2);
}
[serializable]
public abstract class MulticastDelegate : Delegate
{
protected MulticastDelegate(Type target, string method);
protected MulticastDelegate(object target, string method);
protected override Delegate CombineImpl(Delegate follow);
protected override object DynamicInvokeImpl(object[] args);
public override bool Equals(object obj);
public override Delegate[] GetInvocationList();
protected override Delegate RemoveImpl(Delegate value);
public static new bool operator ==(
MulticastDelegate d1, MulticastDelegate d2);
public static new bool operator !=(
MulticastDelegate d1, MulticastDelegate d2);
}
Figure 3 Delegate Class Generated by the C# Compiler
public sealed class StringDelegate : System.MulticastDelegate
{
public StringDelegate (object obj, int method);
public virtual int Invoke(string str);
public virtual IAsyncResult BeginInvoke(string str,
AsyncCallback asc, object stateObject);
public virtual int EndInvoke(IAsyncResult result);
}
Figure 4 Delegate Referencing Private Method
class MyOtherCRT
{
private int strlen(string s)
{return s.Length;}
public StringDelegate GetDel()
{
return new StringDelegate (strlen);
}
}
MyOtherCRT b = new MyOtherCRT();
StringDelegate d = b.GetDel();
int i = d(".NET delegates");
Figure 5 CThreadClass
public class CThreadClass
{
public void StartBigCalc()
{
Thread t = new Thread(new ThreadStart(DoBigCalc));
t.Start();
}
public void DoBigCalc()
{
// do the calculation here
}
}
Figure 6 Using a Separate Object to Pass Data
public class CWorker
{
private int m_i;
public CWorker(int i){m_i = i;}
public void DoBigCalc()
{
// do the calculation here using m_i
}
}
public class CThreadClass2
{
public void StartBigCalc(int i)
{
CWorker w = new CWorker(i);
Thread t = new Thread(new ThreadStart(w.DoBigCalc));
t.Start();
}
}
Figure 7 Returning Data via a Delegate Call
public delegate void InformMe(int i);
public class CWorker2
{
private int m_i;
private InformMe d;
public CWorker2(int i, InformMe dd)
{m_i = i; d = dd;}
public void DoBigCalc()
{
d(CalcReturnVal(m_i));
}
protected int CalcReturnVal(int i);
}
public class CDoAsync3
{
public void StartBigCalc(int i, InformMe d)
{
CWorker2 w = new CWorker2(i, d);
Thread t = new Thread(new ThreadStart(w.DoBigCalc));
t.Start();
}
}
Figure 8 Calling a Method on a Thread
public class CCalling
{
private int m_i;
public void GiveMeAResult(int i)
{
m_i = i;
}
public int DoCalc(int i)
{
CDoAsync3 a = new CDoAsync3();
a.StartBigCalc(i, new InformMe(this.GiveMeAResult));
Thread.Sleep(1000); // hack
return m_i;
}
}
Figure 9 AsyncResult Object
public class AsyncResult : System.Object, IAsyncResult, IMessageSink
{
internal AsyncResult(Message m);
public virtual void SetMessageCtrl(IMessageCtrl mc);
public virtual IMessage GetReplyMessage();
public virtual object AsyncDelegate {get;}
public bool EndInvokeCalled {get; set;}
// IAsyncResult
public virtual bool IsCompleted {get;}
public virtual bool CompletedSynchronously {get;}
public virtual object AsyncState {get;}
public virtual WaitHandle AsyncWaitHandle {get;}
// IMessageSink
public virtual IMessageSink NextSink {get;}
public virtual IMessageCtrl AsyncProcessMessage(
IMessage msg, IMessageSink replySink);
public virtual IMessage SyncProcessMessage(IMessage msg);
}
Figure 11 Calling a Method Asynchronously
Polling for Completion
StringDelegate d = new StringDelegate (A.strlen);
IAsyncResult ar;
ar = d.BeginInvoke("Hello", null, null);
while(!ar.IsCompleted)
{
// do work
}
int i = d.EndInvoke(ar);
// use result
Waiting for Completion
StringDelegate d = new StringDelegate (A.strlen);
IAsyncResult ar;
ar = d.BeginInvoke("Hello", null, null);
// do work
ar.AsyncWaitHandle.WaitOne();
int i = d.EndInvoke(ar);
// use result
Figure 12 Calling a Delegate and Providing a Callback
public class CCaller
{
private void CallMe(IAsyncResult ar)
{
StringDelegate d
= (StringDelegate)((AsyncResult)ar).AsyncDelegate;
int i = d.EndInvoke(ar);
// use result
}
public void DoCall()
{
StringDelegate d = new StringDelegate (A.strlen);
d.BeginInvoke("Hello", new AsyncCallback(CallMe), null);
}
}
Figure 13 Delegates with Ref or Out Parameters
class CAsyncCalls
{
public delegate void times2(ref int i);
public void p(ref int i)
{
i *= 2;
}
public void run()
{
times2 d = new times2(p);
int i = 42;
IAsyncResult ar;
ar = d.BeginInvoke(ref i, null, null);
ar.AsyncWaitHandle.WaitOne();
Console.WriteLine("before EndInvoke {0}", i);
d.EndInvoke(ref i, ar);
Console.WriteLine("after EndInvoke {0}", i);
}
}
Figure 14 Returning Data Through an In Parameter
class App
{
delegate void GetData(byte[] b);
static void GetBuf(byte[] b)
{
for (byte x = 0; x < b.Length; x++)
b[x] = (byte)(x*x);
}
static void Main()
{
GetData d = new GetData(App.GetBuf);
byte[] b = new byte[10];
IAsyncResult ar;
ar = d.BeginInvoke(b, null, null);
ar.AsyncWaitHandle.WaitOne();
for (int x = 0; x < b.Length; x++)
Console.Write("{0} ", b[x]);
}
}
Figure 15 Accessing an HTTP Server Asynchronously
void DumpPage(string host, string page)
{
TcpClient c = new TcpClient(host, http);
String str= "GET " + page + " HTTP/1.0\n\n";
byte[] req = new byte[str.Length + 1];
// convert request to bytes
Encoding.ASCII.GetBytes(str, 0, str.Length, req, 0);
NetworkStream s = (NetworkStream)c.GetStream();
// make the request for the page
s.Write(req, 0, req.Length);
// ask for the data in 1000 byte chunks
byte[] buf = new byte[1000];
while (s.DataAvailable)
{
IAsyncResult asr;
asr = s.BeginRead(buf, 0, buf.Length, null, null);
// do some other work here...
asr.AsyncWaitHandle.WaitOne();
// get the results, read how many bytes were obtained
int count = s.EndRead(asr);
char[] ch = new char[count];
Encoding.ASCII.GetChars(buf, 0, count, ch, 0);
String resp = new String(ch);
Console.Write(resp);
}
}
А еще я подстриглась и теперь похожа на

@музыка: тишина
@настроение: ходила на массаж.....^_^