span.sup { vertical-align:text-top; }

Concurrent Affairs

More AsyncEnumerator Features

Jeffrey Richter

Code download available at:ConcurrentAffairs2008_08.exe(155 KB)

Contents

Joining Concurrent Asynchronous Operations
APM Support
Return Values
Callback Thread Control with Synchronization Contexts
Synchronizing Access to Shared Data
Discard Groups
Cancellation
Wrap-Up

In my November 2007 Concurrent Affairs column, I discussed the idea of using C# language features to simplify asynchronous programming (see msdn.microsoft.com/magazine/cc163323). In particular, I focused on anonymous methods, lambda expressions, and iterators. Then in my June 2008 column, I introduced my AsyncEnumerator class and explained how it can be used to drive a C# iterator (see msdn.microsoft.com/magazine/cc546608). I also walked through Async­Enumerator's architecture and explained how it works internally.

In this column, I want to show you some additional features that AsyncEnumerator provides, such as joining across multiple concurrent asynchronous operations, Asynchronous Programming Model (APM) support, return values, callback thread control, synchronizing access to shared data, automatic discarding of uncompleted operations, and cancellation/timeout support. Along the way, I'll also walk you through some common programming patterns made possible with AsyncEnumerator.

Joining Concurrent Asynchronous Operations

One of the great things about performing asynchronous operations is that you can perform several of them concurrently, greatly improving your application's performance. For example, if you initiate three asynchronous Web service requests concurrently, and if each request takes 5 seconds to complete, then the total time your program spends waiting is just 5 seconds. On the other hand, if you perform synchronous Web service requests, then your application waits for each one to complete before initiating the next. So performing three synchronous Web service requests, each of which takes 5 seconds, means that your application waits at least 15 seconds.

With AsyncEnumerator, it is extremely easy to initiate several concurrent asynchronous operations. Your code can then process the completed operations after all of them complete or as each one completes. The iterator in Figure 1 shows both techniques for processing completed operations. When I ran it, I got the following output (note that you must wait for the Web request to timeout):

All the operations completed:
   Uri=https://wintellect.com/        ContentLength=41207
   Uri=https://www.devscovery.com/    ContentLength=13258
   Uri=https://1.1.1.1/  WebException=Unable to connect to remote server
An operation completed:
   Uri=https://wintellect.com/        ContentLength=41208
An operation completed:
   Uri=https://www.devscovery.com/    ContentLength=13258
An operation completed:
   Uri=https://1.1.1.1/   WebException=Unable to connect to remote server

Figure 1 Coordinating Multiple Asynchronous Operations

public static class AsyncEnumeratorPatterns {
  public static void Main() {
    String[] urls = new String[] { 
      "https://Wintellect.com/", 
      "https://1.1.1.1/",   // Demonstrates error recovery
      "https://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);
    }
    }
}

The code at the beginning of the iterator issues several asynchronous operations and then executes a yield return numOps statement. This statement tells AsyncEnumerator not to call back into your code until the number of operations indicated by the value in the numOps variable have completed. The code just below the yield return statement then executes a loop to process all of the completed operations.

Note that the operations can complete in a different order than they were issued. In order to correlate each result with its request, I passed wr, which identifies the WebRequest object I'm using to initiate the request, in BeginGetResponse's last argument. Then in the ProcessCompletedWebRequest method, I extract the Web­Request object used to initiate the request from the IAsyncResult's AsyncState property.

The code at the bottom of the iterator also issues several asynchronous operations and then enters a loop to process each operation as it completes. However, the iterator must first wait until each operation completes. This is accomplished by the yield return 1 statement which tells AsyncEnumerator to call back into the iterator code as soon as a single operation completes.

APM Support

In my previous column, I explained how calling Async­Enumerator's Execute method starts the execution of an iterator's code. However, I also explained that the thread that calls Execute will block until the iterator exits or executes a yield break statement.

Having the thread block may hurt an application's scalability and is something you'd really want to avoid, especially in server applications. It also hurts responsiveness if called by a GUI thread because the iterator takes an indefinite amount of time to execute, and during this time a Windows ® Forms or Windows Presentation Foundation (WPF) application will stop responding to input. You'd really want to call Execute only when writing test code or when experimenting with an iterator method.

For production code, you should call AsyncEnumerator's Begin­Execute and EndExecute methods. Internally, when you call Begin­Execute, the AsyncEnumerator object constructs an instance of the AsyncResultNoResult class, which I discussed in the March 2007 issue of MSDN ® Magazine (msdn.microsoft.com/magazine/cc163467). When you call BeginExecute, you can pass a reference to your own AsyncCallback method, and the AsyncEnumerator object will invoke this method when the iterator completes its execution. This method should then call AsyncEnumerator's EndExecute method to get the iterator's result. I'll show some examples later where I take advantage of the BeginExecute and EndExecute methods. Here is what the methods look like:

public class AsyncEnumerator<TResult>: AsyncEnumerator {
  public IAsyncResult BeginExecute(
    IEnumerator<Int32> enumerator,
    AsyncCallback callback, Object state);

  public void EndExecute(IAsyncResult result);
}

Also, since AsyncEnumerator supports the APM, it can be integrated with all of the existing Microsoft ® .NET Framework application models since they already support the APM. This means that you can use AsyncEnumerator with ASP.NET Web Form applications, ASP.NET XML Web services, Windows Communication Foundation (WCF) services, Windows Forms applications, WPF applications, console applications, Windows services, and so on.

Another thing worth pointing out is that, since Async­Enumerator supports the APM, it can be used inside another iterator and allow for composition of asynchronous operations. For example, you could implement an iterator that knows how to asynchronously make a database request and process the results when they come in. I call this the subroutine iterator. Then, inside another iterator, you could initiate several database requests by invoking the subroutine iterator in a loop. For each loop iteration, you would construct an AsyncEnumerator and call its BeginExecute method, passing in the name of the subroutine iterator and any additional arguments that you want.

Note that there is an important benefit you get with this model: all the subroutine iterators run concurrently without blocking any threads (unless the underlying implementation of the APM blocks threads, such as if BeginXxx were implemented to queue a delegate to the ThreadPool that blocked until some operation completed). This allows you to create a simple iterator that encapsulates a single asynchronous operation and invoke it from within other iterators while maintaining scalability and responsiveness.

Return Values

In many scenarios, it is useful to have the iterator return a result after all of its processing has completed. However, an iterator cannot return a value when it is finished because an iterator cannot have a return statement in it. And yield return statements return a value for each iteration, not a final value.

If you want to have an iterator return a final value after processing, I have created the helper class AsyncEnumerator<TResult>. The model for this class is shown here:

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);
}

Using AsyncEnumerator<TResult> is straightforward. First, change your code to instantiate an AsyncEnumerator<TResult> instead of the normal AsyncEnumerator. For TResult, specify the type that you want the iterator to ultimately return. Now, modify the portion of your code that calls the Execute or EndExecute method (which used to return void) to obtain the return value and use this value however you want.

Next, modify your iterator code so that it accepts an AsyncEnumerator<TResult> instead of an AsyncEnumerator. Of course, you must specify the same data type for the generic TResult parameter. Finally, inside your iterator code, set the AsyncEnumerator<TResult> object's Result property to whatever value you want returned from the iterator.

To help put all this together, Figure 2 shows code that implements a simple asynchronous ASP.NET Web service that simultaneously requests the HTML for several different Web sites (passed as a comma-separated string). After all the Web site data has been received, the Web service returns an array of strings with each element showing the Web site's URL and the number of bytes downloaded from that Web site, or an error if an error occurred.

Figure 2 Retrieving Multiple Web Sites Concurrently

[WebService(Namespace = "https://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);
  }
}

Callback Thread Control with Synchronization Contexts

As asynchronous operations complete, various thread pool threads wake up to notify your AsyncEnumerator object. If Async­Enumerator used these threads to call back into your iterator, then your iterator's code can be executed by different threads even though an iterator's code will only ever have one thread at a time executing inside it. For some scenarios, having different threads execute the iterator's code is problematic. For example, in a Windows Forms or WPF application, a control must be manipulated by the thread that created the control, and that cannot be a thread pool thread.

Having the iterator code execute via arbitrary thread pool threads can pose another problem. With an ASP.NET application, for example, when a client request first comes in, ASP.NET associates the client's IPrincipal (for impersonation) as well as the Culture information with the thread pool thread's CurrentPrincipal, Current­Culture, and CurrentUICulture properties. However, if you use this thread to call some BeginXxx method, then when the new thread pool thread executes to notify you of the operation's completion, the new thread pool thread will not, by default, have these properties set correctly.

To help solve these problems, the CLR allows each thread to have a SynchronizationContext-derived object associated with it. This object is used to help maintain the threading model employed by an application model. For Windows Forms and WPF, their SynchronizationContext-derived objects know how to marshal a function call (made by a thread pool thread) to the GUI thread. Regarding ASP.NET, its SynchronizationContext-derived object knows how to initialize the principal and culture properties onto each thread pool thread used to process a single request.

To maintain your application's proper threading model, AsyncEnumerator offers a Sync­Context property. This property is initialized to the value returned by Synchronization­Context's static Current property inside Async­Enumerator's constructor. If it is null—as it normally would be for a console or Windows service application—then whenever a thread pool thread calls into the AsyncEnumerator object, the object just uses that thread to call into your iterator. However, if the SyncContext property is not null, then the AsyncEnumerator object has the thread pool thread call your iterator via the specified SynchronizationContext-derived object.

So for Windows Forms and WPF application, this means that your iterator code will always execute via the GUI thread, and, therefore, you can just execute code in your iterator to update controls on the form. There is no need to call Control's Invoke/BeginInvoke methods or Dispatcher's Invoke/BeginInvoke methods. This makes it simple to have your iterator do progress reporting in your UI as asynchronous operations complete. For ASP.NET, this means that the principal and culture properties will always be set correctly when your iterator code executes. The code in Figure 2 and the Windows Forms example I will show you later both take advantage of this feature.

Synchronizing Access to Shared Data

In some scenarios, especially in server-side scenarios, you may have multiple AsyncEnumerator objects (one per client request) each processing their own iterators simultaneously. For example, imagine a Web site that accesses some database and then updates a set of objects in memory. You'll certainly want to use the APM for the database access (calling SqlCommand's BeginExecuteReader, for example), and then you might need to update the in-memory objects in a thread-safe way. Normally you might use methods of the Monitor class or the C# lock statement, or perhaps the Reader­WriterLockSlim that ships with the .NET Framework 3.5. However, all of these locks can block the calling thread, hurting your scalability and responsiveness. To avoid blocking threads, I tend to use my ReaderWriterGate, which I describe in my May 2006 column (see msdn.microsoft.com/magazine/cc163532).

When I started to use ReaderWriterGate with AsyncEnumerator, I realized that the object model of ReaderWriterGate could be improved to make integration with AsyncEnumerator better. So I created a new class, called SyncGate, that behaves very similar to ReaderWriterGate. Here is its model:

   public sealed class SyncGate {
     public SyncGate();
     public void BeginRegion(SyncGateMode mode, 
       AsyncCallback asyncCallback); 
     public void EndRegion(IAsyncResult ar); 
   }
   public enum SyncGateMode { Exclusive, Shared }

If you have multiple iterators running that want to access shared data, first construct a SyncGate and store a reference to it in a static field or somehow get the reference to it out to the various iterators. Then, inside the iterators, just before the code that touches the shared data, call SyncGate's Begin­Region method, indicating whether the code needs exclusive (write) or shared (read) access to the data. Then have the iterator yield return 1. At this point your iterator will give up its thread, and when your iterator can access the data safely, AsyncEnumerator will call back into your code automatically. This means that no threads are blocked while waiting to get access to the shared data.

In your iterator, just after the code that touches the shared data, call EndRegion. This tells SyncGate that your code is finished touching the shared data and allows other iterators the ability to access it if they desire. In Figure 2, the bottom of the GetWebSiteDataLength iterator uses SyncGate to access a static collection exclusively. Also in Figure 2, the GetRecentRequests iterator shows how to gain shared access to the same collection.

Discard Groups

Another feature offered by AsyncEnumerator class is discard groups. This feature allows your iterator to issue several concurrent asynchronous operations and then later decide that it doesn't care about some (or all) of them, causing the AsyncEnumerator object to discard the results automatically as the remaining operations complete.

For example, imagine code that wants to get a city's temperature. There are many Web services that you could query to acquire this information. You could write an iterator that queries three Web services to get this information, and as soon as any of them returns the temperature, you can discard the other two Web services' results. Some people use this pattern to improve the performance of their applications.

Another example is when an iterator performs a sequence of operations and the user just wants to cancel them because he gets tired of waiting or he just changes his mind. A similar scenario exists where you start an asynchronous operation, but if it doesn't complete in some fixed amount of time, you want to discard all operations that haven't completed yet. For example, some Web services process a client's request and if all the processing can't complete in, say, two seconds, the service would rather notify the client that his request failed than have the client wait indefinitely for the response.

Here's how to use discard groups: inside your iterator, you group a bunch of related operations together as part of a discard group. A discard group is just an Int32 value ranging from 0 to 63, inclusive. For example, you can issue a bunch of BeginXxx methods indicating that they are all part of discard group 0. Then your iterator can process some of them as they complete. When you decide in your iterator code that you don't want to process anymore of the operations that are part of this discard group, you call Async­Enumerator's DiscardGroup method:

public void DiscardGroup(Int32 discardGroup);

This method tells the AsyncEnumerator object to discard any remaining operations that were issued as part of the specified discard group so that your iterator code will never see any of these operations when it calls DequeueAsyncResult. Unfortunately, this is not quite good enough, as the .NET APM mandates that EndXxx methods be called for every BeginXxx method, or resources may possibly be leaked.

To address this requirement, AsyncEnumerator must call the right EndXxx method for any operation it discards. Since there is no way that AsyncEnumerator could figure out the correct EndXxx method to call on its own, you must tell it which one. When you call a BeginXxx method, instead of simply passing ae.End for the AsyncCallback argument, you must pass one of these methods:

AsyncCallback End(Int32 discardGroup, EndObjectXxx callback);
AsyncCallback EndVoid(Int32 discardGroup, EndVoidXxx callback);

EndObjectXxx and EndVoidXxx are delegates defined as follows:

delegate Object EndObjectXxx(IAsyncResult result);
delegate void EndVoidXxx(IAsyncResult result);

If a BeginXxx method has a corresponding EndXxx method that returns a value, then you would call the End method just shown. If you call a BeginXxx method that has a corresponding EndXxx method that returns void (the unusual case), then you would call the EndVoid method. Now whenever you tell AsyncEnumerator to discard a group, it will know what EndXxx method to call.

Note that if the EndXxx method throws any exception at all, AsyncEnumerator catches it and swallows it. It does this because if you are discarding the operation, you are indicating that you don't care about whether the operation succeeded or failed.

I should also point out that when your iterator exits or executes a yield break statement, AsyncEnumerator automatically discards all discard groups—if you exit the iterator, then you are indicating that you don't care about any of the operations that have not been processed yet. This can be very convenient because it allows your iterator to issue some asynchronous operations, process however many completed operations it needs, and then just exit.

AsyncEnumerator automatically cleans up any remaining operations that complete in the future. Note, however, that discarding operations doesn't cancel them. If one of the pending asynchronous operations was writing to a file or updating a database, discarding the relevant groups will not prevent those operations from completing. It will simply allow your code to continue without regard for the operations' completions.

Cancellation

AsyncEnumerator allows external code to cancel the iterator during its processing. This feature is especially useful for Windows Forms and WPF applications as it allows the impatient user to cancel an ongoing operation and regain control of the application. AsyncEnumerator also has the ability to cancel itself after a specified amount of time. This feature is useful to server applications that want to set a limit on the amount of time it takes to respond to a client's request. The methods related to cancellation are shown here:

public class AsyncEnumerator {
  // Call this method from inside the iterator
  public Boolean IsCanceled(out Object cancelValue);

  // Call this method from inside the iterator
  public Boolean IsCanceled();

  // Call this method from code outside the iterator
  public Boolean Cancel(Object cancelValue};

  // Call this method from code inside or outside the iterator
  public void SetCancelTimeout(Int32 milliseconds,
    Object cancelValue);
}

To take advantage of cancellation, inside your iterator, issue each asynchronous operation as part of a discard group. This allows the AsyncEnumerator to automatically discard any operations that complete after the cancellation request. Then, to detect a cancellation request, include code similar to that shown in Figure 3 after each yield return statement.

Figure 3 Detecting Cancellation

IEnumerator<Int32> MyIterator(AsyncEnumerator ae, ...) {
  // obj refers to some object that has BeginXxx/EndXxx methods
  obj.BeginXxx(...,         // Pass any arguments as usual
    ae.End(0, obj.EndXxx),  // For AsyncCallback indicate
                            // discard group 0 & proper End method  
                            //to call for cleanup
    null);                  // BeginXxx's AsyncState argument

  // Make more calls to BeginXxx methods if desired here...

  yield return n; // Resume iterator after 'n' operations
                  // complete or if cancelation occurs 

  // Check for cancellation
  Object cancelValue;
  if (ae.IsCanceled(out cancelValue)) {
    // The iterator should cancel due to user request/timeout
    // Note: It is common to call "yield break" here.
  } else {
    // Call DequeueAsyncResult 'n' times to 
    // process the completed operations
  }
}

Now, when you want to start executing a cancelable iterator, you construct an AsyncEnumerator object and call BeginExecute on it just like you normally would. Then, when some part of your application wants to cancel the iterator, it calls the Cancel method. When calling Cancel, you can pass it a reference to an object that is then passed to the iterator by way of the IsCanceled method's out parameter. This object gives the code canceling the iterator a way to communicate to the iterator why it is being canceled. If the iterator doesn't care to know why it is being canceled, then it can call the overloaded IsCanceled method that takes no parameters.

The SetCancelTimeout method can be called by code both inside and outside the iterator. When this method is called, it sets up a timer that will call Cancel automatically (passing the value you specify via the Cancel­Value argument) when the time expires.

The code in Figure 4 shows a Windows Forms application that uses many of the features discussed in this column. The UI for the application is shown in Figure 5. It uses the AsyncEnumerator's SyncContext feature to ensure that all the iterator code executes via the GUI thread allowing UI controls to be updated. This code also shows how to use the Async­Enumerator's APM support so as not to block the GUI thread allowing the UI to remain responsive.

Figure 4 WindowsFormsViaAsyncEnumerator.cs

namespace WinFormUsingAsyncEnumerator {
  public partial class WindowsFormsViaAsyncEnumerator : Form {
    public static void Main() {
      Application.Run(new WindowsFormsViaAsyncEnumerator());
    }

    public WindowsFormsViaAsyncEnumerator() {
      InitializeComponent();
    }

    private AsyncEnumerator m_ae = null;

    private void m_btnStart_Click(object sender, EventArgs e) {
      String[] uris = new String[] {
        "https://Wintellect.com/", 
        "https://1.1.1.1/",   // Demonstrates error recovery
        "https://www.Devscovery.com/" 
      };

      m_ae = new AsyncEnumerator();

      // NOTE: The AsyncEnumerator automatically saves the 
      // Windows Forms SynchronizationContext with it ensuring
      // that the iterator always runs on the GUI thread; 
      // this allows the iterator to access the UI Controls

      // Start iterator asynchonously so that GUI thread doesn't block
      m_ae.BeginExecute(GetWebData(m_ae, uris), m_ae.EndExecute);
    }

    private IEnumerator<Int32> GetWebData(AsyncEnumerator ae, String[] uris) {
      ToggleStartAndCancelButtonState(false);
      m_lbResults.Items.Clear();

      if (m_chkAutoCancel.Checked)
        ae.SetCancelTimeout(5000, ae);

      // Issue several Web requests (all in discard group 0) simultaneously
      foreach (String uri in uris) {
        WebRequest webRequest = WebRequest.Create(uri);

        // If the AsyncEnumerator is canceled, DiscardWebRequest cleans up
        // any outstanding operations as they complete in the future
        webRequest.BeginGetResponse(ae.EndVoid(0, DiscardWebRequest), 
                                                          webRequest);
      }

      yield return uris.Length;  // Process the completed Web requests 
                                 // after all complete

      String resultStatus; // Ultimate result of processing shown to user

      // Check if iterator was canceled
      Object cancelValue;
      if (ae.IsCanceled(out cancelValue)) {
        ae.DiscardGroup(0);
        // Note: In this example calling DiscardGroup above is not mandatory
        // because the whole iterator is stopping execution; causing all
        // discard groups to be discarded automatically.

        resultStatus = (cancelValue == ae) ? "Timeout" : "User canceled";
        goto Complete;
      }

      // Iterator wasn't canceled, process all the completed operations
      for (Int32 n = 0; n < uris.Length; n++) {
        IAsyncResult result = ae.DequeueAsyncResult();

        WebRequest webRequest = (WebRequest)result.AsyncState;

        String s = "URI=" + webRequest.RequestUri + ", ";
        try {
          using (WebResponse webResponse = webRequest. 
                 EndGetResponse(result)) {
                    s += "ContentLength=" + webResponse.ContentLength;
          }
        }
        catch (WebException e) {
          s += "Error=" + e.Message;
        }
        m_lbResults.Items.Add(s);  // Add result of operation to listbox
      }
      resultStatus = "All operations completed.";

    Complete:
      // All operations have completed or cancellation occurred, tell      // user
      MessageBox.Show(this, resultStatus);

      // Reset everything so that the user can start over if they desire
      m_ae = null;   // Reset since we're finished
      ToggleStartAndCancelButtonState(true);
    }

    private void m_btnCancel_Click(object sender, EventArgs e) {
      m_ae.Cancel(null);
      m_ae = null;
    }

    // Swap the Start/Cancel button states
    private void ToggleStartAndCancelButtonState(Boolean enableStart) {
      m_btnStart.Enabled = enableStart;
      m_btnCancel.Enabled = !enableStart;
    }

    private void DiscardWebRequest(IAsyncResult result) {
      // Get the WebRequest object used to initate the request 
      // (see BeginGetResponse's last argument)
      WebRequest webRequest = (WebRequest)result.AsyncState;

      // Clean up the async operation and Close the WebResponse (if no       // exception)
      webRequest.EndGetResponse(result).Close();
    }
  }
}

fig05.gif

Figure 5 The Sample Application

Inside the iterator, many Web requests are issued as part of a discard group, and, since the UI stays responsive, the user can click the Cancel button if she gets tired of waiting for the result. If this happens, the AsyncEnumerator will autocomplete any of the operations so that your iterator code doesn't have to worry about doing any cleanup. Note that the form also demonstrates how to set up a timer so that the AsyncEnumerator will cancel itself automatically after five seconds if all the operations have not completed.

This sample makes Web requests using the .NET WebRequest class. When calling WebRequest's Begin­GetResponse method, cleanup requires more than just calling EndGetResponse. You also must call Close (or Dispose) on the WebResponse object that EndGetResponse returns.

For this reason, the code passes the DiscardWeb­Request method to the EndVoid method when calling BeginGetResponse. The DiscardWebRequest method ensures that the WebResponse object is closed if making the Web request was successful and doesn't throw an exception.

Many developers know that asynchronous programming is the key to increasing the performance, scalability, responsiveness, and reliability of their applications, servers, and components. Unfortunately, many developers refuse to fully embrace asynchronous programming because the programming model has been so much more tedious and difficult than the tried-and-true synchronous programming model.

Using the C# iterator feature and my Async­Enumerator class allows developers to embrace asynchronous programming from within a synchronous programming model. Async­Enumerator also integrates easily with other parts of the .NET Framework and offers many features that allow developers to extend their applications beyond what is possible with the usual synchronous programming model.

I've been using AsyncEnumerator for more than a year and have helped many companies integrate it into their software with excellent results. Download the code from https://www.wintellect.com/blogs/JeffreyR/power-threading-library-asyncenumerator-syncgate-net-rocks-and-sideshow. I hope you gain as much from it as I have.

Send your questions and comments for Jeffrey to mmsync@microsoft.com.

Jeffrey Richter a cofounder of Wintellect (www.Wintellect.com), an architecture review, consulting and training firm. He is the author of several books, including CLR via C#. Jeffrey is also a contributing editor to MSDN Magazine and has been consulting with Microsoft since 1990.