Дополнительные возможности AsyncEnumerator
ОГЛАВЛЕНИЕ
Присоединение к параллельным асинхронным операциям
Одной из положительных сторон выполнения асинхронных операций является то, что несколько из них можно выполнять параллельно, намного улучшая производительность приложения. Например, если три асинхронных запроса веб-служб инициализировать параллельно и если на завершение каждого запроса уходит 5 секунд, то общее время, которое программа проведет в ожидании – всего 5 секунд. С другой стороны, в случае выполнения синхронных запросов веб-служб приложению придется ждать завершения каждого из них перед инициализацией следующего. Так что выполнение трех синхронных запросов веб-служб, каждый из которых занимает 5 секунд, означает, что приложение будет ждать как минимум 15 секунд.
С помощью AsyncEnumerator очень просто инициализировать несколько параллельных асинхронных операций. Код после этого может обработать завершенные операции после завершения их всех, или по мере завершения каждой из них. В итераторе на рис. 1 показаны обе методики обработки завершенных операций. Запустив его, я получил следующий результат (обратите внимание, что необходимо ждать истечения времени веб-запроса):
All the operations completed:
Uri=http://wintellect.com/ ContentLength=41207
Uri=http://www.devscovery.com/ ContentLength=13258
Uri=http://1.1.1.1/ WebException=Unable to connect to remote server
An operation completed:
Uri=http://wintellect.com/ ContentLength=41208
An operation completed:
Uri=http://www.devscovery.com/ ContentLength=13258
An operation completed:
Uri=http://1.1.1.1/ WebException=Unable to connect to remote server
Рис. 1. Координация нескольких асинхронных операций
public static class AsyncEnumeratorPatterns {
public static void Main() {
String[] urls = new String[] {
"http://Wintellect.com/",
"http://1.1.1.1/", // Demonstrates error recovery
"http://www.Devscovery.com/"
};// Demonstrate process
AsyncEnumerator ae = new AsyncEnumerator();
ae.Execute(ProcessAllAndEachOps(ae, urls));
}private static IEnumerator<Int32> ProcessAllAndEachOps(
AsyncEnumerator ae, String[] urls) {
Int32 numOps = urls.Length;// Issue all the asynchronous operation(s) so they run concurrently
for (Int32 n = 0; n < numOps; n++) {
WebRequest wr = WebRequest.Create(urls[n]);
wr.BeginGetResponse(ae.End(), wr);
}// Have AsyncEnumerator wait until ALL operations complete
yield return numOps;Console.WriteLine("All the operations completed:");
for (Int32 n = 0; n < numOps; n++) {
ProcessCompletedWebRequest(ae.DequeueAsyncResult());
}Console.WriteLine(); // *** Blank line between demos ***
// Issue all the asynchronous operation(s) so they run concurrently
for (Int32 n = 0; n < numOps; n++) {
WebRequest wr = WebRequest.Create(urls[n]);
wr.BeginGetResponse(ae.End(), wr);
}for (Int32 n = 0; n < numOps; n++) {
// Have AsyncEnumerator wait until EACH operation completes
yield return 1;Console.WriteLine("An operation completed:");
ProcessCompletedWebRequest(ae.DequeueAsyncResult());
}
}private static void ProcessCompletedWebRequest(IAsyncResult ar) {
WebRequest wr = (WebRequest)ar.AsyncState;
try {
Console.Write(" Uri=" + wr.RequestUri + " ");
using (WebResponse response = wr.EndGetResponse(ar)) {
Console.WriteLine("ContentLength=" + response.ContentLength);
}
}
catch (WebException e) {
Console.WriteLine("WebException=" + e.Message);
}
}
}
Код в начале итератора выдает несколько асинхронных операций и затем исполняет оператор yield return numOps. Этот оператор указывает AsyncEnumerator не проводить обратных вызовов к коду, пока не будет закончено столько операций, сколько указано значением переменной numOps. Код сразу под оператором yield return затем исполняет цикл для обработки всех завершенных операций.
Отметьте, что порядок завершения операций может отличаться от порядка их выдачи. Для соотнесения каждого результата с его запросом я передал переменную wr, которая определяет объект WebRequest, используемый мною для инициации запроса, в последнем аргументе BeginGetResponse. Затем в методе ProcessCompletedWebRequest я извлекаю объект WebRequest, использованный для инициации запроса, из свойства AsyncState объекта IAsyncResult.
Код внизу итератора также выдает несколько асинхронных операций и затем входит в цикл для обработки каждой операции по мере ее завершения. Однако итератор должен сперва дождаться завершения каждой операции. Это выполняется оператором yield return 1, который указывает AsyncEnumerator произвести обратный вызов к коду итератора, как только завершится одна из операций.