Дополнительные возможности AsyncEnumerator - Возвращаемые значения

ОГЛАВЛЕНИЕ

Возвращаемые значения

Во многих ситуациях полезно, чтобы итератор возвращал результат по завершении всех его процессов. Однако, итератор не может возвратить значение по завершении, поскольку в итераторе не может быть возвращаемого оператора. А операторы yield return возвращают значение для каждой итерации, не окончательное значение.

Если необходимо, чтобы по завершении обработки итератор возвращал окончательное значение, то можно воспользоваться моим вспомогательным классом AsyncEnumerator<TResult>. Модель этого класса показана здесь:

public class AsyncEnumerator<TResult>: AsyncEnumerator {
  public AsyncEnumerator();
  public TResult Result { get; set; }

  new public TResult Execute(IEnumerator<Int32> enumerator);
  new public TResult EndExecute(IAsyncResult result);
}

Использовать AsyncEnumerator<TResult> довольно просто. Сперва измените код, чтобы он создавал экземпляр AsyncEnumerator<TResult> вместо нормального AsyncEnumerator. Для TResult укажите тип, который итератору следует возвращать в итоге. Далее, измените часть кода, вызывающую метод Execute или EndExecute (который ранее возвращал void), чтобы получить возвращаемое значение, и используйте это значение как угодно.

Далее, измените код итератора, чтобы он принимал AsyncEnumerator<TResult> вместо AsyncEnumerator. Само собой, необходимо указать тот же тип данных для универсального параметра TResult. Наконец, внутри кода итератора установите свойство Result объекта AsyncEnumerator<TResult> на значение, которое должен возвратить итератор.

Чтобы помочь в сведении всего этого вместе, на рис. 2 показан код, реализующий простую асинхронную веб-службу ASP.NET, которая одновременно запрашивает код HTML для нескольких различных веб-узлов (передаваемых как разделенная запятыми строка). После получения всех данных о веб-узлах веб-служба возвращает массив строк, где каждый элемент показывает URL-адрес веб-узла и число байтов, загруженных с веб-узла, либо ошибку, если произошла ошибка.

Рис. 2. Одновременное получение нескольких веб-узлов

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService {
  private static List<String[]> s_recentRequests = new List<String[]>(10);
  private static SyncGate s_recentRequestsSyncGate = new SyncGate();

  private AsyncEnumerator<String[]> m_webSiteDataLength;

  [WebMethod]
  public IAsyncResult BeginGetWebSiteDataLength(
   String uris, AsyncCallback callback, Object state) {

   // Construct an AsyncEnumerator that will eventually return a String[]
   m_webSiteDataLength = new AsyncEnumerator<String[]>();

   // NOTE: The AsyncEnumerator automatically saves the ASP.NET 
   // SynchronizationContext with it ensuring that the iterator 
   // always executes using the correct IPrincipal, 
   // CurrentCulture, and CurrentUICulture.

   // Initiate the iterator asynchronously. 
   return m_webSiteDataLength.BeginExecute(
    GetWebSiteDataLength(m_webSiteDataLength, uris.Split(',')), 
              callback, state);
   // NOTE: Since the AsyncEnumerator's BeginExecute method returns an 
   // IAsyncResult, we can just return this back to ASP.NET
  }

  private IEnumerator<Int32> GetWebSiteDataLength(
       AsyncEnumerator<String[]> ae, String[] uris) {

   // Issue several web request simultaneously
   foreach (String uri in uris) {
    WebRequest webRequest = WebRequest.Create(uri);
    webRequest.BeginGetResponse(ae.End(), webRequest);
   }

   yield return uris.Length; // Wait for ALL the web requests to complete

   // Construct the String[] that will be the ultimate result
   ae.Result = new String[uris.Length];

   for (Int32 n = 0; n < uris.Length; n++) {
    // Grab the result of a completed web request
    IAsyncResult result = ae.DequeueAsyncResult();

    // Get the WebRequest object used to initate the request 
    WebRequest webRequest = (WebRequest)result.AsyncState;

    // Build the String showing the result of this completed web request
    ae.Result[n] = "URI=" + webRequest.RequestUri + ", ";

    using (WebResponse webResponse = webRequest.EndGetResponse(result)) {
     ae.Result[n] += "ContentLength=" + webResponse.ContentLength;
    }
   }

   // Modify the collection of most-recent queries
   s_recentRequestsSyncGate.BeginRegion(SyncGateMode.Exclusive, ae.End());
   yield return 1;  // Continue when collection can be updated (modified)

   // If collection is full, remove the oldest item
   if (s_recentRequests.Count == s_recentRequests.Capacity)
    s_recentRequests.RemoveAt(0);

   s_recentRequests.Add(ae.Result);
   s_recentRequestsSyncGate.EndRegion(ae.DequeueAsyncResult());  
  // Updating is done //
  }

  // ASP.NET calls this method when the iterator completes. 
  [WebMethod]
  public String[] EndGetWebSiteDataLength(IAsyncResult result) {
   return m_webSiteDataLength.EndExecute(result);
  }

  private AsyncEnumerator<String[][]> m_aeRecentRequests;

  [WebMethod]
  public IAsyncResult BeginGetRecentRequests(AsyncCallback callback, 
                       Object state) {
   m_aeRecentRequests = new AsyncEnumerator<String[][]>();
   return m_aeRecentRequests.BeginExecute(GetRecentRequests(m
    _aeRecentRequests), callback, state);
  }

  private IEnumerator<Int32> GetRecentRequests(
   AsyncEnumerator<String[][]> ae) {
   // In a shared way, read the collection of most-recent requests
   s_recentRequestsSyncGate.BeginRegion(SyncGateMode.Shared, ae.End());
   yield return 1;  // Continue when collection can be examined (read)

   // Return a copy of the collection as an array
   ae.Result = s_recentRequests.ToArray();
   s_recentRequestsSyncGate.EndRegion(ae.DequeueAsyncResult());  
// Reading is done
  }

  [WebMethod]
  public String[][] EndGetRecentRequests(IAsyncResult result) {
   return m_aeRecentRequests.EndExecute(result);
  }
}