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

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

Последний параметр определяет, что делегат зарегистрирован навсегда (точнее в данном случае до момента уничтожения экземпляра класса Test, в конструкторе которого выполняется регистрация). Если бы последний параметр равнялся true, регистрация делегата была бы действительна только на один вызов.

Метод MyCallBack, ссылка на который передана конструктору при создании делегата myCallBackDelegate, прежде всего выводит на консоль информацию о потоке, в котором он исполняется. Как и раньше это хеш потока и данные о его типе. В данном случае мы полагаем, что значение последнего параметра при каждом вызове этого метода будет равно true, так как выполняться этот метод должен рабочим потоком.

Далее MyCallBack выводит на консоль некоторое сообщение. Это сообщение состоит из следующих частей:

1. Префикс

Ради демонстрации того, как можно объявлять делегат нового типа, префикс формируется излишне сложно — посредством использования делегата нового типа — MyDelegate. Из его объявления

2. private delegate String MyDelegate ();

видно, что с делегатом данного типа можно связать любую функцию, возвращающую String и не имеющую аргументов. В теле метода MyCallBack создается новый делегат hello

MyDelegate hello = new MyDelegate(MyHello);

которому передается ссылка на метод MyHello этого же класса Test. Именно этот метод и формирует префикс вида Tеst_XXX:, где вместо XXX будет подставляться порядковый номер текущего вызова метода MyCallBack.

3. Порядковый номер вызова данного метода

При каждом вызове метода MyCallBack счетчик count увеличивается на единицу.

4. Информация о причине вызова данного метода

Через параметр timedOut метод MyCallBack получает от системы информацию о причине его вызова. Если получено значение false, то этот метод был вызван благодаря тому, что кто-то установил событие myEvent в состояние signaled. Значение true будет получено в том случае, если метод был вызван по причине завершения срока ожидания.

Последний метод NewEvent класса Test как раз и может использоваться клиентами для перевода события _myEvent в состояние signaled.

Теперь вновь обратимся к коду метода Main.

После создания экземпляра test класса Test вызывается его метод NewEvent в результате чего из пула рабочих потоков извлекается новый поток, который и выполняет метод MyCallBack. Напомним, что после этого событие _myEvent автоматически переходит в начальное состояние.

Далее основной поток засыпает на 500 тс. В связи с тем, что интервал ожидания, заданный четвертым параметром в ThreadPool.RegisterWaitForSingleObject равен 100 mc, метод MyCallBack будет вызван несколько раз по причине завершения периода ожидания.

Далее во второй раз вызывается метод NewEvent, и MyCallBack вызывается по причине перехода события myEvent в состояние signaled.

И, наконец, основной поток засыпает еще на 1000 mс, в течении которых MyCallBack вызывается с интервалом 100 mс по причине завершения времени ожидания.

Через 1000 mс основной поток просыпается и выполнение всего приложения (включая все рабочие потоки) завершается.

Ниже приводится вывод на консоль, полученный после запуска данного приложения:

>>> МуАрр thread = 16 IsPoolThread = False

>>> Test constructor thread = 16 IsPoolThread = False

>>> MyCallback thread = 18 IsPoolThread = True

Test_1: Count = 1 timedOut = False

>>> MyCallback thread = 18 IsPoolThread = True

Test_2: Count = 2 timedOut = True

>>> MyCallback thread = 18 IsPoolThread = True

Test_3: Count = 3 timedOut = True

>>> MyCallback thread = 18 IsPoolThread = True

Test_4: Count = 4 timedOut = True

>>> MyCallback thread = 18 IsPoolThread = True

Test_5: Count = 5 timedOut = True

>>> MyCallback thread = 18 IsPoolThread = True

Test_6: Count = 6 timedOut = True





>>> MyCallback thread = 18 IsPoolThread = True

Test_7: Count = 7 timedOut = False

>>> MyCallback thread = 18 IsPoolThread = True

Test_8: Count = 8 timedOut = True

>>> MyCallback thread = 18 IsPoolThread = True

Test_9: Count = 9 timedOut = True

>>>MyCallback thread = 18 IsPoolThread = True

Test_10: Count = 10 timedOut = True

>>> MyCallback thread = 18 IsPoolThread = True

Test_11: Count = 11 timedOut = True

>>> MyCallback thread = 18 IsPoolThread = True

Test_12: Count = 12 timedOut = True

>>> MyCallback thread = 18 IsPoolThread = True

Test_13: Count = 13 timedOut = True

>>>MyCallback thread = 18 IsPoolThread = True

Test_14: Count = 14 timedOut = True

>>> MyCallback thread = 18 IsPoolThread = True

Test_15: Count = 15 timedOut = True

>>> MyCallback thread = 18 IsPoolThread = True

Test 16: Count = 16 timedOut = True

Возвращаемся к коду инициализации атрибута

Теперь можно более подробно обсудить код метода InitIfNecessary. Все тело этого метода включено в критическую секцию lock(this) {}. Здесь this является ссылкой на экземпляр текущего класса (SynchronizationAttribute), который и является собственно свойством синхронизации (как контекста, так и домена синхронизации). Таким образом, при входе текущего потока в данную критическую секцию никакой другой поток не может войти в эту секцию (и в любую другую типа lock (obj) {}, где obj является ссылкой на данное свойство синхронизации).

Далее проверяется условие _asyncWorkEvent == null. Это условие выполняется только тогда, когда текущее свойство синхронизации еще не инициализовано, т. е. в данный момент формируется новый домен синхронизации и текущее свойство будет его свойством синхронизации. Именно в этом случае выполняется инициализация свойства. В противном случае код инициализации пропускается, т. к. текущее свойство уже инициализирование ранее.

Инициализация состоит из следующих шагов:

• Создается экземпляр _asyncWorkEvent события AutoResetEvent

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

• Создается экземпляр _workItemQueue очереди Queue

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

• Создается список _asyncLcidList

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

• Создается делегат callBackDelegate типа WaitOrTimerCallBack

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

• Регистрация делегата callBackDelegate и события _asyncWorkEvent в пуле рабочих потоков

Для регистрации используется статический метод RegisterWaitForsingieObject класса ThreadPool. Третий параметр в вызове данного метода равен null, что говорит о том, что функции DispatcherCallBack не передаются никакие данные. Величина интервала ожидания timeout, по истечении которого автоматически вызывается делегат (если ранее состояние _asyncWorkEvent не было переведено в состояние signaled), задается при инициализации атрибута синхронизации и доступна только для чтения: