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

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

• _cliCtxAttr!= null

В этом случае cliCtxAttr является ссылкой на свойство синхронизации старого контекста и именно эта ссылка включается в сообщение ctorMsg как ссылка на свойство синхронизации нового контекста. Таким образом оба контекста синхронизации (старый и новый) имеют одно и то же свойство синхронизации. В этом случае говорят, что оба контекста принадлежат одному домену синхронизации.

Теперь уместно отметить одну особенность домена синхронизации, связанную с реентерабельностью. Предположим, что активируется экземпляр o1 некоторого класса, описанного с атрибутом

[Synchronization(REQUIRIES_NEW, true)]}.

В этом случае формируется новый контекст синхронизации, который начинает собой и новый домен синхронизации. До активации объекта o1 активируется новый экземпляр атрибута синхронизации. В процессе выполнения его конструктора в поле _bReEntrant атрибута синхронизации сохраняется значение true. Таким образом, созданный контекст синхронизации является реентерабельным контекстом.

Предположим теперь, что при выполнении некоторого метода объекта o1 активируется экземпляр о2 некоторого класса, описанного с атрибутом [synchronization ()]. Это означает, что объект о2 должен жить в контексте синхронизации (флаг required), но не допускается реентерабельность. В зависимости от других атрибутов, приписанных классам, экземплярами которых являются объекты o1 и о2, эти объекты будут жить в одном контексте, либо в различных контекстах, но в одном домене синхронизации. Это определяется тем, что реентерабельность никак не учитывается при определении границ домена синхронизации. В связи с тем, что все контексты одного домена синхронизации имеют одно на всех свойство контекста с именем "Synchronization", а это свойство в рассматриваемом примере разрешает реентерабельность, объект о2 против желания его разработчика оказывается в реентерабельном контексте, что может привести к ошибке.

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

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

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

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

Вызов может быть как синхронным, так и асинхронным. В обоих случаях перехватчик входящих вызовов формирует соответствующую данному вызову работу (экземпляр класса WorkItem), в которой сохраняется:

• Само сообщение

• Идентификатор контекста (данного домена синхронизации), в который пришел вызов

• Контекст логического вызова

• Ссылка на перехватчик входящих вызовов, которому нужно передать сообщение для дальнейшей обработки

• Перехватчик результатов, на который нужно послать результат (для асинхронного вызова)

Дальнейшая судьба работы определяется ее типом и наличием в домене синхронизации других работ, ожидающих выполнения:

• Если домен синхронизации нереентерабельный и работа представляет собой вложенный вызов (для синхронного вызова, который сейчас выполняется в данном домене), то данная работа сразу же выполняется без какого-либо ожидания. Заведомо известно, что в данный момент в данном домене синхронизации не выполняется ни один другой поток.

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

• Асинхронный вызов всегда записывается в очередь (даже если она пуста и домен не блокирован).

Работы извлекаются из очереди, если в домене нет исполняемой работы (например, предыдущая работа завершена).

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

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

Важный практический вывод. Различие между синхронными и асинхронными вызовами состоит в том, что в случае синхронного вызова клиент блокируется в ожидании ответа, а в случае асинхронного не ожидает ответа (его уведомление о результате вызова возлагается на специальный перехватчик). Таким образом разработчик может надеяться на то, что синхронные вызовы могут обрабатываться в домене синхронизации в приоритетном порядке по отношению к асинхронным. Однако в случае реализации атрибута синхронизации в рамках SSCLI это не так — попавшие в очередь (единую для всего домена синхронизации) работы извлекаются из нее в порядке очереди, и тип работы (синхронный или асинхронный вызов) не влияет на этот порядок.

Эксперименты

Эксперименты с вышеописанным кодом должны продемонстрировать работу сервисов синхронизации и трассировки вызовов.





Все компоненты размещаются в одном контексте

Первый эксперимент не требует какой-либо модификации кода клиента и сервера. Каждому компоненту (Account, Tax, News) приписаны два атрибута:

[Synchronization ()]

[MyCallTrace("LogFile")]

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

Запустив серверное приложение и параллельно два клиентские приложения, мы увидим на консоли сервера информацию о том, что все три компонента выполняются в контексте 1.

Ниже приведены несколько первых строк с консоли сервера

Server is listening

News context = 1 News constructor thread = 3 IsPoolThread = True

Tax context = 1 Tax constructor thread = 3 IsPoolThread = True

Account context = 1 Account constructor thread = 3 IsPoolThread = True

Tax notification: new Account operation: +5

Tax Notify thread = 3 IsPoolThread = True

News notification: new Account operation: +5

News Notify thread = 3 IsPoolThread = True

News notification: direct notification from Account

News Notify thread = 3 IsPoolThread = True

Account Add thread = 3 IsPoolThread = True

Tax notification: new Account operation: +5

Tax Notify thread = 65 IsPoolThread = True

News notification: new Account operation: +5

News Notify thread = 65 IsPoolThread = True

News notification: direct notification from Account

News Notify thread = 65 IsPoolThread = True