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

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

                         _asyncWorkEvent.Set();

                    }

              }

       }

       else {

          …..

       }

  }

 else {

      work.SetSignaled();

      work.Execute();

   }

}

Итак, если вызов не вложенный, происходит следующее. Блокируется очередь работ (текущий поток входит в критическую секцию)

lock (workltemQueue) {

……

}

и текущая работа помечается как ожидающая в очереди work.Setwaiting();

Потом эта работа становится в очередь работ _workltemQueue.Enqueue(work);

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

if ((!_locked) &&

     (_workItemQueue.Count == 1)) {

      work.SetSignaled();

      _locked = true;

      _asyncWorkEvent.Set();

}

Если же очередь была не пуста, то только-что поставленная в эту очередь работа ждет своей очереди и будет извлечена из нее в свое время.

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

internal virtual void HandleWorkRequest(Workitem work) {

      bool bQueued;

      if (!IsNestedCall(work._reqMsg)) {

         ……

       }

       else {

            work.SetSignaled();

             work.Execute();

       }

}

Возникающие при этом проблемы уже обсуждались при рассмотрении синхронного случая.

Теперь рассмотрим ту ветвь кода метода HandleWorkCompletion(), которая связана с обработкой асинхроннных вызовов (в асинхронном случае этот метод будет вызван из DispatcherCallBack, который будет выполняться рабочим потоком, инициированным переводом свойства _asyncWorkEvent в состояние signaled):

internal virtual void HandleWorkCompletion() {

      Workltem nextWork = null;

       bool bNotify = false;

       lock (_workItemQueue) {

             if (_workItemQueue.Count >= 1) {

                 nextWork = (Workltem) _workltemQueue.Peek();

                 bNotify = true;

                  nextWork.SetSignaled();

              }

              else {

                    _locked = false;

              }

         }

          if (bNotify) {

                if (nextWork.IsAsync()) {

                      _asyncWorkEvent.Set ();





                }

                else {

                         ……

                }

        }

}

Критическая секция

lock (workItemQueue) {

……

}

уже была рассмотрена ранее.

Пусть теперь в начале очереди находится асинхронная работа (nextWork). В этом случае событие asyncWorkEvent устанавливается в состояние signaled и на этом вся подготовка к обработке новой работы завершается.

Перехват исходящего вызова

Формирование перехватчика исходящих вызовов

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

Класс SynchronizationAttribute реализует интерфейс IContributeClientContextSink.

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

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

Ниже приводится кодметода GetClientContextSink из Rotor:

public virtual IMessageSink GetClientContextSink (

     IMessageSink nextSink) {

      InitlfNecessary();

      SynchronizedClientContextSink propertySink =

           new SynchronizedClientContextSink (

                  this,

                  nextSink);

       return (IMessageSink) propertySink;

}

Этот код аналогичен коду метода GetServerContextSink, в связи с чем комментарии опущены.

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

Класс SynchronizedClientContextSink наследует классу InternalSink и реализует интерфейс IMessageSink. Его основная функциональность определяется двумя методами интерфейса IMessageSink: SyncProcessMessage и AsyncProcessMessage, обрабатывающими соответственно синхронные и асинхронные исходящие вызовы.

Перехват исходящих синхронных вызовов

Случай реентерабельного контекста

Начнем со случая реентерабельного контекста (домена). Вот соответствующая ветвь кода метода SyncProcessMessage:

public virtual IMessage SyncProcessMessage(

         IMessage reqMsg) {

         IMessage repiyMsg;

         if (_property.IsReEntrant) {

              _property.HandleThreadExit();

              replyMsg = _nextSink.SyncProcessMessage(reqMsg);

              _property.HandleThreadReEntry();

          }

          else {

                 ……

          }

          return replyMsg;

}

Прежде всего нужно уведомить свойство синхронизации (_property)

_property.HandleThreadExit();

о том, что выполняется вызов за пределы текущего контекста. Это позволит свойству синхронизации инициировать выполнение очередной работы. Рассмотрим код соответствующего метода HandleThreadExit класса SynchronizationAttribute:

internal virtual void HandleThreadExit() {

      HandleWorkCompletion();

}

Код для HandleWorkCompletion уже рассматривался. В результате его выполнения будет проверено состояние очереди работ. Если она не пуста, то очередная работа будет помечена флагом готовности к выполнению. В противном случае домен синхронизации будет разблокирован, что просто означает возможность выполнения вновь поступившего синхронного вызова без записи в очередь. Далее в случае наличия готовой к выполнению работы ее выполнение инициируется. В случае асинхронной работы для этого достаточно перевести событие _asyncWorkEvent в состояние signaled, а в случае синхронной — разбудить занятый ее выполнением процесс путем вызова Monitor.Pulse (nextWork), где nextWork — ссылка на готовую к выполнению синхронную работу.