Интернет-журнал "Домашняя лаборатория", 2007 №6
Шрифт:
Рассмотрим заголовок класса SynchronizationAttribute:
SynchronizationAttribute: ContextAttribute, IContгibuteServerContexts ink, IContributeClientContextSink
Уже отсюда видно, что данный атрибут должен обеспечивать двух перехватчиков. Один будет встроен в цепочку перехватчиков, перехватывающих все поступающие в контекст вызовы, а другой в цепочку, перехватывающих все исходящие за пределы контекста вызовы.
Прежде всего необходимо остановиться на трех понятиях:
• Домен синхронизации
До сих пор мы говорили, что объекты с одинаковыми
Домен синхронизации состоит из одного или нескольких контекстов. Однако даже в случае нескольких контекстов при входе какого-либо потока в любой контекст домена синхронизации доступ для всех остальных потоков в любой контекст данного домена синхронизации заблокирован. Иными словами, все объекты из одного домена синхронизации, даже если они живут в разных контекстах, используют один и тот же сервис синхронизации.
Естественен вопрос — почему нельзя поместить все эти объекты в один контекст? Причина в том, что возможность активации некоторого объекта в некотором старом контексте (в том контексте, в котором выполняется поток, активирующий новый объект), определяется соотношением набора запросов к сервисам со стороны нового объекта и набора сервисов, имеющихся в старом контексте. Старый контекст может быть контекстом синхронизации, новый объект также может требовать синхронного доступа к своим методам. Однако этот новый объект может требовать дополнительного сервиса, например, трассировки вызовов, которого не было в старом контексте. Следовательно, новый объект должен быть активирован в новом контексте, который также должен быть контекстом синхронизации.
Если старый и новый контексты синхронизации независимы, то возможно параллельное обращение к двум объектам из этих двух контекстов. Однако возможны ситуации, когда это недопустимо и необходимо обеспечить синхронный доступ к объектам, принадлежащим разным контекстам.
Например, в случае продажи общего дома семейной парой нельзя параллельно договариваться о продажи дома и с мужем, и с женой, т. к. общий ресурс — дом имеется только в одном экземпляре. Если объекты, представляющие мужа и жену, живут в различных контекстах, то вызов метода продать дом на объекте муж должен блокировать вызов этого же метода на объекте жена.
• Вложенные вызовы
Получив внешний вызов, пересекающий границу любого контекста домена синхронизации, этот домен синхронизации блокируется. Однако возможна ситуация, когда для некоторых новых внешних вызовов блокировка домена синхронизации должна быть снята. Это так называемые вложенные вызовы. При выполнении некоторого внешнего вызова X, поступившего в некоторый контекст домена синхронизации, может быть сделан вызов X1 за пределы этого контекста. При выполнении вызова X1 где-то в другом домене может быть сделан вызов Х2 опять в некоторый контекст данного домена синхронизации. Нельзя блокировать выполнение вызова Х2 до завершения выполнения вызова х, т. к. последний ожидает выполнения вызова X1, который в свою очередь ожидает выполнения вызова Х2. Вся эта цепочка вложенных вызовов образует один так называемый логический вызов, имеющий уникальный идентификатор. По этому идентификатору домен синхронизации распознает вновь пришедший вызов как вложенный вызов и не блокирует его выполнение.
• Реентерабельность
Предположим, что домен синхронизации блокирован на время выполнения вызова X. Пусть в процессе выполнения X был сделан вызов X1 за пределы домена синхронизации. Обычно ни один поток не может выполнить какой-либо работы в данном домене синхронизации пока не будет получен ответ или не придет вложенный вызов для вызова xi. Однако в ряде случаев можно разрешить принимать во время ожидания другие внешние вызовы, никак не связанные с вызовом X. В этом случае домен синхронизации называется реентерабельным (reentrant). Естественно, только разработчик некоторого класса может указать, что экземпляры данного класса могут жить в реентерабельном домене синхронизации. Разработка таких классов способствует повышению эффективности проектируемой системы.
Теперь обратимся к конструктору класса SynchronizationAttribute. Для этого класса имеется четыре конструктора. Рассмотрим прежде четвертый конструктор, т. к. первые три делегируют вызов четвертому.
public SynchronizationAttribute(int flag, bool reEntrant)
: base(PROPERTY_NAME) {
_bReEntrant = reEntrant;
switch (flag) {
case NOT_SUPPORTED:
case SUPPORTED:
case REQUIRED:
case REQUIRES_NEW:
_flavor = flag;
break;
default:
throw new ArgumentException(
Environment.GetResourceString(
"Argument_InvalidFlag"),
"flag");
}
}
Первый параметр flag принимает одно из четырех возможных значений:
• NOT_SUPPORTED (== 0x00000001)
Данный флаг означает, что экземпляр класса, которому приписан атрибут синхронизации с соответствующим флагом в конструкторе, не должен активироваться в каком-либо контексте синхронизации.
• SUPPORTED (== 0x00000002)
Данный флаг означает, что разработчик не заботится о том, в каком именно контексте (синхронизации или нет) будет активирован экземпляр его класса.
• REQUIRED (== 0x00000004)
Данный флаг означает, что экземпляр класса с соответствующим атрибутом всегда должен активироваться в контексте синхронизации.
• REQUIRES_NEW (== 0x00000008)
Данный флаг означает необходимость создания нового контекста синхронизации для нового экземпляра класса.
Если аргумент flag содержит какое-либо иное значение, генерируется соответствующее исключение.
Второй логический аргумент равен true, если экземпляр класса, которому приписан данный атрибут, может жить в реентерабельном контексте. В противном случае второй аргумент равен false.
В конструкторе класса SynchronizationAttribute вызывается конструктор базового класса ContextAttribute с аргументом property_name (строковой константой равной "Synchronization"). Именно так называется свойство синхронизации в SSCLI.