Шрифт:
Далее клиент получает результаты завершенного асинхронного вызова
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 не содержит никаких методов и данный интерфейс используется просто как маркер классов, допускаемых для передачи в контексте вызова за пределы контекста, домена приложения и т. п.