_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 — ссылка на готовую к выполнению синхронную работу.