Интернет-журнал "Домашняя лаборатория", 2007 №6
Шрифт:
Тут все в порядке, если только инкапсулированный вызов вложен в исполняемый синхронный вызов. Если же этот инкапсулированный вызов вложен в некоторый исходящий асинхронный вызов, то могут быть проблемы. Например, в данное время в данном домене синхронизации уже может выполняться некоторый синхронный вызов и параллельное выполнение еще одного вызова противоречит логике использования домена синхронизации (?!).
Теперь настало время рассмотреть код ранее упомянутого метода 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);
}
}
}
}
Данный
Прежде всего блокируется очередь работ. Если она не пуста, мы получаем ссылку на работу (nextWork), стоящую в этой очереди первой (не извлекая ее из очереди) и устанавливаем флаг готовности данной работы к выполнению. Если же очередь пуста, домен синхронизации разблокируется (_locked = false). Это означает, что вновь пришедший синхронный вызов будет обработан сразу же без постановки в очередь.
Если в начале очереди находится синхронная работа (nextWork), то это означает, что имеется поток, который находится в состоянии ожидания того момента, когда работа nextWork будет готова к выполнению. Этот поток в свое время впал в состояние ожидания находясь в некоторой критической секции, в которую он попал заблокировав объект nextWork. Находясь в этой критической секции он был должен освободить эту блокировку, так как в то время работа nextWork была поставлена в очередь работ и этот поток не мог продолжить ее обработку. Теперь его пора разбудить. Это делает текущий поток, в котором выполняется код метода HandleWorkCompletion:
lock(nextWork) {
Monitor.Pulse(nextWork);
}
Текущий поток входит в критическую секцию (заблокировав nextWork) и уведомляет все заинтересованные потоки о том, что состояние объекта nextWork как-то изменилось. Эти находящиеся в состоянии ожидания потоки переходят в состояние готовности и по выходе текущего потока из данной критической секции пытаются захватить контроль над объектом nextWork. В нашем случае такой поток единственный и именно он ответственен за продолжение обработки вызова, инкапсулированного в работу nextWork. Конкретнее, это поток, выполняющий код метода HandieWorkRequest начиная со строки, следующей за строкой Monitor.Wait(work);
Как перехватчик обрабатывает асинхронные вызовы
Вновь возвращаемся к классу SynchronizedServerContextSink, который реализует интерфейс IMessageSink. В этом интерфейсе объявлен метод AsyncProcessMessage, который и должен реализовать обработку асинхронных вызовов в перехватчике входящих вызовов. Вот так этот метод реализован в классе SynchronizedServerContextSink:
public virtual IMessageCtrl AsyncProcessMessage(
IMessage reqMsg, IMessageSink replySink) {
Workltem work = new WorkItem (
reqMsg,
_nextSink,
replySink);
work.SetAsync;
_property.HandleWorkRequest(work);
return null;
}
В данном случае мы имеем два входных параметра. Первый задает вызов в форме сообщения типа IMessage (как и в случае синхронного вызова). А вот второй параметр специфичен для асинхронных вызовов. Он задает ссылку на еще один перехватчик, который ответственен за доставку уведомления о завершении асинхронного вызова. И, наконец, возвращаемое значение имеет также специфический для асинхронных вызовов тип — IMessageCtrl. В .NET интерфейс IMessageCtrl объявляет единственный метод Cancel, с помощью которого можно прервать выполнение асинхронного вызова. В данном случае метод AsyncProcessMessage всегда возвращает null, не предоставляя такую возможность.
Функциональность данного метода вполне понятна. Пришедший асинхронный вызов инкапсулируется в работу work, далее для нее устанавливается флаг асинхронности, позволяющий различать синхронные и асинхронные работы, и, наконец, данная работа направляется на обработку в свойство синхронизации _property.
То, как метод HandleWorkRequest обрабатывает синхронные запросы, уже было рассмотрено ранее. Теперь изучим ветви его кода, ответственные за обработку асинхронных запросов.
Как свойство синхронизации обрабатывает инкапсулированный асинхронный вызов, полученный от перехватчика
Ниже приведена часть кода HandleWorkRequest, которая относится к обработке именно асинхронных вызовов:
internal virtual void HandieWorkRequest(Workltem work) {
bool bQueued;
if (!IsNestedCall(work._reqMsg)) {
if (work.IsAsync) {
bQueued = true;
lock (workltemQueue) {
work.SetWaiting;
_workltemQueue.Enqueue(work);
if ((!_locked) & &
(_workItemQueue.Count == 1)) {
work.SetSignaled;
_locked = true;
_asyncWorkEvent.Set;
}
}
}
else {
…..
}
}
else {
work.SetSignaled;
work.Execute;
}
}
Итак, если вызов не вложенный, происходит следующее. Блокируется очередь работ (текущий поток входит в критическую секцию)