Шрифт:
Интервал:
Закладка:
}
Данный метод вызывается по завершении обработки некоторой работы. В это время домен синхронизации еще блокирован (_locked == true).
Прежде всего блокируется очередь работ. Если она не пуста, мы получаем ссылку на работу (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();
}
}
Итак, если вызов не вложенный, происходит следующее. Блокируется очередь работ (текущий поток входит в критическую секцию)
lock (workltemQueue) {
……
}
и текущая работа помечается как ожидающая в очереди work.Setwaiting();
Потом эта работа становится в очередь работ _workltemQueue.Enqueue(work);
и, если домен синхронизации не заблокирован и данная работа в очереди единственна, инициируется ее выполнение. Для этого после установки флага готовности работы к выполнению и блокировки домена событие _asyncWorkEvent переводится в состояние signaled. Это приведет к тому, что свободный рабочий поток из пула потоков начнет выполнять метод DispatcherCallBack, в процессе чего данная работа и будет извлечена из очереди и отправлена на выполнение:
if ((!_locked) &&
(_workItemQueue.Count == 1)) {
work.SetSignaled();
_locked = true;
_asyncWorkEvent.Set();
}
Если же очередь была не пуста, то только-что поставленная в эту очередь работа ждет своей очереди и будет извлечена из нее в свое время.
Если вызов вложенный, то инкапсулирующая его работа выполняется сразу же без постановки в очередь:
internal virtual void HandleWorkRequest(Workitem work) {
bool bQueued;
if (!IsNestedCall(work._reqMsg)) {
……
}
else {
work.SetSignaled();
work.Execute();
}
}
Возникающие при этом проблемы уже обсуждались при рассмотрении синхронного случая.
Теперь рассмотрим ту ветвь кода метода HandleWorkCompletion(), которая связана с обработкой асинхроннных вызовов (в асинхронном случае этот метод будет вызван из DispatcherCallBack, который будет выполняться рабочим потоком, инициированным переводом свойства _asyncWorkEvent в состояние signaled):
internal virtual void HandleWorkCompletion() {
Workltem nextWork = null;
bool bNotify = false;
lock (_workItemQueue) {
if (_workItemQueue.Count >= 1) {
nextWork = (Workltem) _workltemQueue.Peek();
bNotify = true;
nextWork.SetSignaled();
}
else {
_locked = false;
}
}
if (bNotify) {
if (nextWork.IsAsync()) {
_asyncWorkEvent.Set ();
}
else {
……
}
}
}
Критическая секция
lock (workItemQueue) {
……
}
уже была рассмотрена ранее.
Пусть теперь в начале очереди находится асинхронная работа (nextWork). В этом случае событие asyncWorkEvent устанавливается в состояние signaled и на этом вся подготовка