Дополнительные возможности 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);
}
}