Добавить в цитаты Настройки чтения

Страница 229 из 372

Смысл первых трех параметров описан в предыдущем пункте.

Четвертый параметр служит для передачи ссылки на делегат типа AsyncCallback, который будет использоваться инфраструктурой асинхронных вызовов для вызова callback функции на стороне клиента для уведомления последнего о завершении вызова. Этот параметр может быть задан как null. В этом случае клиент может использовать другие способы получения уведомления о завершении вызова.

Последний параметр задает ссылку на некоторый объект, которая будет доступна из объекта, полученного как результат инициирования асинхронного метода. В данном случае мы будем тут задавать ссылку на делегат sum. Это позволит клиенту в рамках callback функции SumCallback получить доступ к делегату sum и завершить асинхронный вызов, вызвав метод EndInvoke. Если callback функция не используется, этот параметр можно задать равным null.

Возвращаемое значение типа IAsyncResult дает клиенту ссылку на объект, который может использоваться последним для получения информации о выполнении асинхронного вызова. В частности, свойство bool Completed {get; } интерфейса IAsyncResult может использоваться клиентом для опроса инфраструктуры асинхронных вызовов — возвращаемое значение равно true, если вызов завершен. Это один из способов получить информацию о завершении асинхронного вызова без использования callback функции.

• public bool EndInvoke(out int, IAsyncResult)

Данный метод вызывается клиентом для завершения асинхронного вызова Server::Sum и получения результатов. В нашем случае вызов этого метода выполняется в callback функции SumCallback.

Первый параметр — результат операции сложения, возвращаемое логическое значение равно true, если в процессе вычислений не произошло переполнения.

Последний параметр типа IAsyncResult задается клиентом. Это ссылка, полученная клиентом в результате инициирования асинхронного вызова путем вызова метода BeginInvoke.

Клиент. Обработка уведомления о завершении асинхронного вызова

Теперь, обсудив механизм инициирования асинхронного вызова, следует рассмотреть вопрос о обработке уведомления о его завершении. Остановимся для примера на методе

private static void SumCallback(IAsyncResult ar) {… }

Этот метод вызывается инфраструктурой асинхронных вызовов по завершении асинхронного вызова, инициированного в Client::Main следующим образом:

IAsyncResult arSum = sum.Beginlnvoke(3, 4, out sumResult, sumCallback, sum);

В результате в качестве единственного параметра в SumCallback передается ссылка ajrSum на объект типа IAsyncResult, содержащий необходимую клиенту информацию о выполненном асинхронном вызове.

Прежде всего клиент использует полученную ссылку для получения ссылки на делегат sum:

HardFunction2Args sum = (HardFunction2Args)ar.AsyncState;

Далее клиент получает результаты завершенного асинхронного вызова

bool result = sum.Endlnvoke(out z, ar);

и выводит их на консоль

if (result) Console.WriteLine ("SumCallback: Sum = " + z);

else Console.WriteLine("SumCallback: Bad arguments for Server.Sum");

Все завершается увеличением на 1 статического счетчика workCount, служащего для подсчета числа выполненных асинхронных вызовов.

Клиент. Что он делает полезного во время ожидания

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

while (workCount < 2) {

Console.WriteLine("Client thread: count = "+ count++);

Thread.Sleep(100);

}

Именно тут клиент выполняет некоторую работу в ожидании завершения обоих асинхронных вызовов. До тех пор, пока счетчик числа завершенных асинхронных вызовов workCount не достигнет значения 2, клиент выводит на консоль очередное положительное целое с интервалом в 100 тс.

Результаты приведены ниже:

Client thread =16; PoolThread = False

Client thread: count = 0





Server (Sum method) thread =25; PoolThread = True

Client thread: count = 1

Client thread: count = 2

Client thread: count = 3

Client thread: count = 4

Client thread: count = 5

Server (MultBy2 method) thread =27; PoolThread = True

Client thread: count = 6

Client thread: count = 7

Client thread: count = 8

Client thread: count = 9

Client thread: count = 10

SumCallback: Sum = 7

Client thread: count = 11

Client thread: count = 12

Client thread: count = 13

Client thread: count = 14

Client thread: count = 15

MultCallback: MultBy2 = 10

Bye!

Обсуждение результатов:

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

• Серверные методы (Sum и MuitBy2) выполняются асинхронно в различных рабочих потоках.

• Во время выполнения асинхронного вызова (от его инициирования до вызова соответствующей callback функции должно пройти 1000 mс) клиент успевает вывести на консоль 10 строк с очередными значениями переменной count.

• Завершается основной поток по завершении последнего асинхронного вызова. Это обеспечивается за счет подсчета числа завершенных вызовов. Заметим, что рабочий поток не может пережить основной поток. Если бы основной поток не контролировал завершение рабочих потоков и завершился бы раньше, то не завершенные рабочие потоки были бы уничтожены.

• Выполнение второго асинхронного вызова начинается с некоторой задержкой. Это связано с тем, что при наличии непустой очереди работ новый рабочий поток формируется через 500 mс после возникновения необходимости в нем. Ранее созданный поток уничтожается, если он никому не понадобился в течении 30 с. Общее число потоков в пуле не должно превышать 25 (на один процесс).

Немного про контекст вызова

Понятие логического вызова связано с цепочкой вызовов, инициирующих друг друга. Эта цепочка может пересекать границы между контекстами, доменами приложений, процессами и машинами. Все вызовы в этой цепочке связаны одним идентификатором — идентификатором логического вызова, что позволяет системе отличать вызовы, относящиеся к различным логическим цепочкам.

С каждым вызовом обычно связана передача некоторых входных и выходных параметров и возвращаемого значения. Кроме того, с вызовом можно связать дополнительный набор свойств, передаваемый от вызывающей стороне вызываемой стороне и обратно — от вызываемой стороны вызывающей стороне. Этот набор свойств называется контекстом вызова. Использование контекста вызова позволяет клиенту и серверу обмениваться данными, не объявленными явно в сигнатуре вызываемого метода. При определенных условиях контекст вызова может пересекать границы между контекстами, доменами приложений, процессами и машинами, сопровождая логический вызов. На каждом этапе можно добавлять в контекст вызова новые свойства, получать их значения и/или удалять старые свойства.

Свойство контекста вызова состоит из пары (имя свойства, значение свойства). Имя свойства должно иметь тип System.String, а в качестве его значения можно задать ссылку на любой объект (производный от System.Object). Если предполагается передача контекста вызова через границу контекста, домена приложения, процесса, машины, значение каждого передаваемого свойства должно быть экземпляром класса, определенного с атрибутом Serializable и производного от ILogicaiThreadAffinative. Определение интерфейса ILogicalThreadAffinative не содержит никаких методов и данный интерфейс используется просто как маркер классов, допускаемых для передачи в контексте вызова за пределы контекста, домена приложения и т. п.