Глава.29 .Примитивные.конструкции.синхронизации.потоков
// Асинхронное инициирование всех запросов
var httpClient = new HttpClient();
foreach (var server in m_servers.Keys) {
m_ac.AboutToBegin(1);
httpClient.GetByteArrayAsync(server)
.ContinueWith(task => ComputeResult(server, task));
}
// Сообщаем AsyncCoordinator, что все операции были инициированы
// и что он должен вызвать AllDone после завершения всех операций,
// вызова Cancel или тайм-аута
m_ac.AllBegun(AllDone, timeout);
}
private void ComputeResult(String server, Task task) {
Object result;
if (task.Exception != null) {
result = task.Exception.InnerException;
} else {
// Обработка завершения ввода-вывода - здесь или в потоке(-ах) пула
// Разместите свой вычислительный алгоритм...
result = task.Result.Length; // В данном примере
} // просто возвращается длина
// Сохранение результата (исключение/сумма)
// и обозначение одной завершенной операции
m_servers[server] = result;
m_ac.JustEnded();
}
// При вызове этого метода результаты игнорируются
public void Cancel() { m_ac.Cancel(); }
// Этот метод вызывается после получения ответа от всех веб-серверов,
// вызова Cancel или тайм-аута
private void AllDone(CoordinationStatus status) {
switch (status) {
case CoordinationStatus.Cancel:
Console.WriteLine("Operation canceled.");
break;
case CoordinationStatus.Timeout:
Console.WriteLine("Operation timed out.");
break;
case CoordinationStatus.AllDone:
Console.WriteLine("Operation completed; results below:");
foreach (var server in m_servers) {
Console.Write("{0} ", server.Key);
Object result = server.Value;
if (result is Exception) {
Console.WriteLine("failed due to {0}.", result.GetType().Name);
} else {
835
Конструкции.пользовательского.режима
Cons
ole.WriteLine("returned {0:N0} bytes.", result);
}
}
break;
}
}
}
Этот код непосредственно не задействует
Interlocked
-методы, так как весь ко-
ординирующий код инкапсулирован в класс
AsyncCoordinator
. Я подробнее опишу
его ниже, а пока расскажу, что этот класс делает. В процессе конструирования класс
MultiWebRequest
инициализирует класс
AsyncCoordinator
и словарь с набором
URI серверов (и их будущих результатов). Затем он асинхронно выполняет все
веб-запросы. Сначала вызывается метод
AboutToBegin
класса
AsyncCoordinator
,
которому передается количество запланированных запросов
1
. Затем происходит
инициирование запроса вызовом метода
GetByteArrayAsync
класса
HttpClient
.
Метод возвращает объект
Task
, для которого я вызываю
ContinueWith
, чтобы при
получении ответа сервера полученные байты параллельно обрабатывались методом
ComputeResult
во многих потоках пула. После завершения всех запросов к веб-
серверам вызывается метод
AllBegun
класса
AsyncCoordinator
, которому передается
имя метода, который следует запустить после выполнения всех операций (
AllDone
),
а во-вторых, продолжительность тайм-аута. После ответа каждого сервера различные
потоки пула будут вызывать метод
ComputeResult
класса
MultiWebRequests
. Этот
метод обрабатывает байты, возвращенные сервером (или любые ошибки), и сохра-
няет результат в словаре. После сохранения каждого результата вызывается метод
JustEnded
класса
AsyncCoordinator
, позволяющий объекту
AsyncCoordinator
узнать о завершении операции.
После завершения всех операций объект
AsyncCoordinator
вызывает метод
AllDone
для обработки результатов, полученных со всех веб-серверов. Этот метод
будет выполняться потоком пула, последним получившим ответ с сервера. В случае
завершения времени ожидания или отмены операции метод
AllDone
будет вызван
либо потоком пула, уведомляющим объект
AsyncCoordinator
о том, что время
закончилось, либо потоком, вызвавшим метод
Cancel
. Существует также вероят-
ность, что поток, выполняющий запрос к серверу, сам вызовет метод
AllDone
, если
последний запрос завершится до вызова метода
AllBegin
.
Имейте в виду, что в данном случае имеет место ситуация гонки, так как воз-
можно одновременное завершение всех запросов к серверам, вызов метода
AllBegun
,
завершение времени ожидания и вызов метода
Cancel
. Если такое произойдет, объ-
ект
AsyncCoordinator
выберет победителя, гарантируя, что метод
AllDone
будет
вызван не более одного раза. Победитель указывается передачей в метод
AllDone
аргумента состояния, роль которого может играть одно из символических имен,
определенных в типе
CoordinationStatus
:
internal enum CoordinationStatus { AllDone, Timeout, Cancel };
1
Код будет работать корректно, даже если вызвать метод m_ac.AboutToBeging(m_requests.
Length) всего один раз перед циклом вместо вызова метода AboutToBegin внутри цикла.
836
Do'stlaringiz bilan baham: |