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

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

Рассмотрим определенный В ЭТОМ же классе SynchronizationAttribute метод IsNestedCall.

internal bool IsNestedCall(IMessage reqMsg) {

       bool bNested = false;

       if (!IsReEntrant) {

            String lcid = SyncCallOutLCID;

             if (lcid!= null) {

                  LogicalCallContext callCtx =

                     (LogicalCallContext)

                           reqMsg.Properties[Mes sage.CallContextKey];

                 if (callCtx!=null &&

                      lcid.Equals(callCtx.RemotingData.LogicalCalllD)) {

                      bNested = true;

                  }

           }

           if (IbNested && AsyncCallOutLCIDList.Count>0) {

                LogicalCallContext callCtx =

                    (LogicalCallContext)

                          reqMsg.Properties[Message.CallContextKey];

                if (AsyncCallOutLCIDList.Contains(

                    callCtx.RemotingData.LogicalCalllD)) {

                 bNested = true;

                 }

           }

    }

    return bNested;

}

Этот метод возвращает true если текущий контекст (точнее, текущий домен синхронизации) нереентерабельный, а новый вызов (reqMsg) является вложенным для исполняемого в данный момент синхронного вызова, или для одного из исходящих асинхронных вызовов. Во всех остальных случаях возвращается false.

Вначале выясняется синхронность выполняемого в данный момент вызова:

String lcid = SyncCallOutLCID;

if (lcid!= null) {

      ......

}

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

Теперь в случае синхронности исполняемого вызова мы получаем доступ к контексту вызова для вызова reqMsg:

LogicalCallContext callCtx =

      (LogicalCallContext)

              reqMsg.Properties[Message.CallContextKey];

Надо заметить, что тут имеется ввиду класс Message из пространства имен System.Runtime.Remoting.Messaging. Реализация такого класса в этом пространстве имен имеется в Rotor, но отсутствует в .NET. Статическое поле CallContextKey равно __CallContext.

Если доступ к контексту вызова получен, то проверяется, что идентификатор логического вызова исполняемого в данный момент синхронного вызова lcid совпадает с идентификатором логического вызова для вызова reqMsg. В случае их совпадения флаг вложенности bNested получает значение true:

if (callCtx!=null &&

        lcid.Equals(callCtx.RemotingData.LogicalCalllD)) {

        bNested = true;

    }

Здесь ОПЯТЬ приходится отметить, что в .NET у класса LogicalCallContext нет свойства RemotingData типа CallContextRemotingData, нет и самого класса CallContextRemotingData и его свойства LogicalCallID.

Если в данный момент не выполняется синхронный вызов, или новый вызов не является вложенным для исполняемого синхронного вызова, то начинается проверка вложенности нового вызова в один из исходящих асинхронных вызовов.

Выполнение условия 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 первая в очереди и нет препятствий для ее выполнения.