Шрифт:
Интервал:
Закладка:
Если в данный момент не выполняется синхронный вызов, или новый вызов не является вложенным для исполняемого синхронного вызова, то начинается проверка вложенности нового вызова в один из исходящих асинхронных вызовов.
Выполнение условия AsyncCallOutLCIDList.count>0 означает, что список идентификаторов логических вызовов, соответствующих исходящим асинхронным вызовам, не пуст. Свойство AsyncCallOutLCIDList типа ArrayList возвращает ссылку на этот список.
В этом случае как и ранее выясняется контекст вызова reqMsg и проверяется, что соответствующий ему идентификатор логического вызова содержится в списке исходящих асинхронных вызовов. В случае успеха флаг bNested получает значение true:
LogicalCallContext callCtx =
(LogicalCallContext)
reqMsg.Properties[Message.CallContextKey];
if (AsyncCallOutLCIDList.Contains(
callCtx.RemotingData.LogicalCalllD)) {
bNested = true;
}
Возвращаемся вновь к коду метода HandleWorkRequest, который мы рассматриваем в данный момент только для случая инкапсулированного синхронного вызова.
Здесь возможны два случая:
• IsNestedCall(work._reqMsg) == false
Это случай соответствует реентерабельному контексту или невложенному вызову.
В случае синхронности инкапсулированного вызова его нужно поставить в очередь (или выполнять без постановки в очередь, если в данный момент времени в данном домене синхронизации не выполняется ни один поток и очередь работ пуста).
• IsNestedCall(work._reqMsg) == true
А вот в этом случае при синхронности инкапсулированного в work вызова и его вложенности в исполняемый синхронный вызов инкапсулированный вызов следует выполнять сразу же без постановки в очередь, так как его выполнения ожидает основной выполняемый в данный момент синхронный вызов.
Другая возможность — инкапсулированный вызов является вложенным для исходящего асинхронного вызова. Но можно ли этот инкапсулированный вызов исполнять вне очереди и в этом случае? Отложим обсуждение этого вопроса.
Итак, в первом случае (не вложенный вызов), поток входит в критическую секцию
lock(work) {
……
}
заблокировав доступ к работе work, инкапсулирующей входящий вызов. В рамках этой критической секции поток ожидает входа в критическую секцию, для доступа к которой надо заблокировать очередь работ:
lock(_workltemQueue) {
……
}
Войдя в эту вторую критическую секцию, поток проверяет — имеется ли блокировка домена синхронизации. Поле _locked атрибута синхронизации равно false в том случае, когда после выполнения очередной работы обнаруживается, что очередь работ пуста, то есть нет работы, которую следует начинать выполнять в рамках данного домена синхронизации.
Если домен синхронизации не заблокирован, и очередь работ пуста, то домен блокируется, но флаг постановки работы в очередь bQueued не задается (работа может быть выполнена сразу же):
if ((!_locked) &&
(_workltemQueue.Count == 0)) {
_locked = true;
bQueued = false;
}
В противном случае задается флаг bQueued постановки работы в очередь, в самой работе задается флаг, указывающий на то, что она стоит в очереди (work.SetWaiting ()) и выполняется реальная запись работы в очередь:
else {
bQueued = true;
work.SetWaiting();
_workltemQueue.Enqueue(work);
}
Теперь поток выходит из второй (внутренней) критической секции, разблокировав очередь работ. Но что ему делать дальше?
Если работа была поставлена в очередь, то в силу синхронности вызова данный поток должен сам перейти в состояние ожидания, из которого он выйдет только тогда, когда только что поставленная в очередь работа продвинется в ее начало и будет готова для выполнения:
if (bQueued == true) {
Monitor.Wait(work);
if (!work.IsDummy()) {
DispatcherCaiiBack(null, true);
}
else {
lock(workltemQueue) {
_workltemQueue.Dequeue();
}
}
}
Вызов Monitor.Wait (work) переведет текущий поток в состояние ожидания, причем этот поток освободит ранее заблокированную им работу work и будет ожидать сигнала, говорящего о том, что состояние объекта work изменилось, нужно проснуться и продолжить работу с этим объектом. Это сигнал будет выдан другим потоком, заметившим, что работа work первая в очереди и нет препятствий для ее выполнения.
Следующая за Monitor.Wait(work); строка кода будет выполняться уже разбуженным потоком, который вновь получает исключительный доступ к объекту work. Если данная работа не является работой-заглушкой (об этом позже), то вызывается уже рассмотренный метод DispatcherCallBack, который и извлечет эту работу из очереди, выполнит ее и инициирует выполнение следующей работы. В случае же работы-заглушки просто блокируется очередь работ и эта работа-заглушка удаляется из очереди.
А вот что происходит с работой, которую не пришлось ставить в очередь:
else {
if (!work.IsDummy()) {
work.SetSignaled();
ExecuteWorkltem(work);
HandleWorkCompletion();
}
}
Если это не заглушка, устанавливается флаг готовности к выполнению, работа выполняется и потом все подготавливается для выполнения следующей работы.
Здесь надо бы рассмотреть код для метода HandleWorkCompletion(), который собственно и подготавливает все для выполнения следующей работы, но в очередной раз отложим это рассмотрение на потом.
Теперь рассмотрим второй случай, когда вызов синхронный вложенный, а контекст нереентерабельный. Такой вызов надо выполнять вне очереди:
if (!IsNestedCall(work._reqMsg)) {
}
else {
work.SetSignaled();
work.Execute();
}
Тут устанавливается флаг готовности к выполнению, работа выполняется, но никакой подготовки к выполнению следующей работы не проводится, т. к. уже имеется находящийся в состоянии выполнения вызов, ожидающий выполнения данного синхронного вызова.
Тут все в порядке, если только инкапсулированный вызов вложен в исполняемый синхронный вызов. Если же этот инкапсулированный вызов вложен в некоторый исходящий асинхронный вызов, то могут быть проблемы. Например, в данное время в данном домене синхронизации уже может выполняться некоторый синхронный вызов и параллельное выполнение еще одного вызова противоречит логике использования домена синхронизации (?!).
Теперь настало время рассмотреть код ранее упомянутого метода HandleWorkCompletion(). Как и ранее рассмотрим только ту ветвь, которая связана с обработкой синхронных вызовов.
internal virtual void HandleWorkCompletion() {
Workitem nextWork = null;
bool bNotify = false;
lock (_workItemQueue) {
if (_workItemQueue.Count >= 1) {
nextWork = (Workitem) _workltemQueue.Peek();
bNotify = true;
nextWork.SetSignaled();
}
else {
_locked = false;
}
}
if (bNotify) {
if (nextWork.IsAsync()) {
.......
}
else {
lock(nextWork) {
Monitor.Pulse(nextWork);
}
}