Дополнительные возможности AsyncEnumerator

ОГЛАВЛЕНИЕ

В этой статье я хочу продемонстрировать некоторые дополнительные функции, предоставляемые AsyncEnumerator, такие как соединение между несколькими параллельными асинхронными операциями, поддержку модели асинхронного программирования (Asynchronous Programming Model – APM), возвращаемые значения, управляемые потоком обратного вызова, синхронизированный доступ к общим данным, автоматическое удаление незавершенных операций и поддержка отмены/времени ожидания. Попутно я также разберу для читателей некоторые распространенные шаблоны программирования, ставшие возможными с помощью 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 произвести обратный вызов к коду итератора, как только завершится одна из операций.