Description
In his blog Michael Entin talks about using C# iterators to simplify writing of asynchronous code. Be sure to read his article before proceeding!
If you want to do a web request using the WebRequest class, you can either do this synchronously, like this one:
1 publicstring DoRequest(string uri)
2 {
3 WebRequest request = WebRequest.Create(uri);
4
5 WebResponse response = request.GetResponse();
6
7 using (Stream stream = response.GetResponseStream())
8 {
9 byte[] buffer = newbyte[4096];
10 StringBuilder result = newStringBuilder();
11 do
12 {
13 stream.Read(buffer, 0, buffer.Length);
14
15 if (bytesRead > 0)
16 {
17 result.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
18 }
19 } while (bytesRead != 0);
20
21 return result.ToString();
22 }
23 }
Very nice and elegant. But a waste of resources. The operation blocks the caller (and with it probably the UI). The solution to this is to perform the entire request asynchronously: Use BeginGetRequestStream, EndGetRequestStream, and the other BeginXXX and EndXXX methods. You will end up with many callback methods for each of them. At the end you'll have a very complicated set of methods, which will be hard to debug.
Michael (and other) had the ingenious idea to use C# iterators to merge these two concepts, and take the best of both worlds together. The AsyncOperation class in this library implements his approach, at least the basic principles.
Creative misuse…
The basic idea is to implement the operation (e.g. downloading a website) as an iterator method:
1 privateIEnumerable<OperationAction> DownloadUrlInternal(string url)
2 {
3 WebRequest request = WebRequest.Create(Url);
4
5 IAsyncResult ar = request.BeginGetResponse(null, null);
6
7 ProgressUpdate<Status> update = newProgressUpdate<Status>(Status.ReceivingResponse, ar.AsyncWaitHandle);
8 yieldreturn update;
9
10 WebResponse response = request.EndGetResponse(ar);
11 }
This method implements an iterator and returns an operation action of some sort. After a call to BeginGetResponse, the method creates a ProgressChangedEventStateBag which contains the status of the current request, as well as the Waithandle returned by the BeginGetResponse. Using the 'yield return' statement, this information is passed to the caller.
The method is not resumed until the Waithandle is signaled.
The DowndloadUrl method is not called directly by an application. Instead, a wrapper class is used, called AsyncOperation. This class ensures that the DownloadUrl method is called properly and translates those OperationActions into events like ProgressChanged, Completed, Error and Timedout.
The wrapper is written this way:
1 publicAsyncOperation<Status, string> DownloadUrl(string url)
2 {
3 returnnewAsyncOperation<Status, string>(DownloadUrlInternal(url, 3000);
4 }
This call initializes a new AsyncOperation class with a timeout of 3 seconds. The calling application can use this code to run the code:
1 helper = reader.DownloadUrl(tbUrl.Text);
2
3 helper.ProgressChanged += helper_ProgressChanged;
4 helper.Finished += helper_Finished;
5 helper.Error += helper_Error;
6
7 helper.Run();
The entire source code, as well as a sample application can be downloaded from the bottom of this page.
The AsyncOperation are intensively used in my InfiniTec.Exchange project.
More Information
The AsyncOperation classes
The main classes are the AsyncOperation and those derived from AsyncOperation. The most important members are displayed in this diagram:
(click to enlarge)
Most of the logic is implemented in the AsyncOperation class. However, only the AsyncOperation<StatusType> and the AsyncOperation<StatusType, ResultType> classes can be used. The first one is best suited for operation which do not yield a result. For example copying an input stream to an output stream. An example for this is the AsyncOperations.Copy
operation, which does exactly this. During progress updates, it returns the current operation (reading from the input stream or writing to the output stream), as well as the current progress, if the source stream supports seeking.
The second operation type reports status updates and returns a result. A good example for this would be the download of a file using the HttpWebRequest operation.
The operation actions
An asynchronous operation can return several different operation actions. These are displayed below:
(click to enlarge)
- The ProgrssUpdate<StatusType> action is used to report progress changes to the caller. This is also the action that drives the operation. Every BeginXXX method should be followed by a ProgressUpdate action containing the IAsyncResult.AsyncWaitHandle.
- The abstract ProgressUpdate action is only used internally.
- The OperationResult<ResultType> is used to report the result of the operation.
- The NestedOperation can be used to run an asynchronous operation from another asynchronous operation. The current operation is not resumed until the nested operation has completed. The calling operation can specifiy wether progress updates are propagated to the root operation. Addionally, the calling operation can specify, wehter it wants to handle a possible timeout of the nested operation and certain exception. Unhandled exceptions and timeouts are propagated up the call chain until a handler is found or until the root operation is reached.
- The NestedOperation<StatusType> is a combination of the NestedOperation action and the ProgressUpdate<StatusType> action. This type will first report the progress update and then execute the nested operation.
Progress updates of nested operations
The main problem of propagating the progress of nested operations to the root operation is the possible incompatible types of the status types. This is solved by marking properties of the status classes of the parent operation as a nested state. The NestedState attribute is used for this purpose. During a nested progress update, the status class of the parent operation is searched for a matching property which is marked with the NestedState attribute.
ChangeLog
Version 2.0 (05/15/2006)
This version is a major update… somewhere in the middle I lost track of the changes…. Some of the changes are:
- New class AsyncOperations. Contains a basic Stream-to-Stream copy operation
- Changed the name of the AsyncOperationBase class to AsyncOperation
- Added an Update method to the ProgressUpdate class
- The type of the Timeout property has been changed from int to TimeSpan
- The StatusBag classes have benn renamed to OperationActions.
- Nested operation are now better supported
- Progress updates can now be reported from a nested operation to the root operation.
- New Class AsyncOperation<StatusType> for operations which don't return a result
- WaitForComletion operation added to the AsyncOperation class. Blocks the calling thread until the operation is finished.
Version 1.1 (03/31/2006)
- Added a WaitHandle that is signaled, when the status of the operation changes. This allows better nesting of asynchronous operations.
- Added Status, StatusInformation, Result and Exception properties. Now the current statatus can be read from the AsyncOperation class.
- Added Abort method to the AsyncOperation class.
- OnFinished in OnCompleted umbenannt
- Added NDoc documentation
Version 1.0 (03/30/2006)
Downloads