An asynchronous operation that uses the IAsyncResult design pattern is implemented as two methods named BeginOperationName and EndOperationName that begin and end the asynchronous operation OperationName respectively. For example, the FileStream class provides the BeginRead and EndRead methods to asynchronously read bytes from a file. These methods implement the asynchronous version of the Read method.
After calling BeginOperationName, an application can continue executing instructions on the calling thread while the asynchronous operation takes place on a different thread. For each call to BeginOperationName, the application should also call EndOperationName to get the results of the operation.
Beginning an Asynchronous Operation
The BeginOperationName method begins asynchronous operation OperationName and returns an object that implements the IAsyncResult interface. IAsyncResult objects store information about an asynchronous operation. The following table shows information about an asynchronous operation.
| Member | Description |
|---|---|
| An optional application-specific object that contains information about the asynchronous operation. | |
| A WaitHandle that can be used to block application execution until the asynchronous operation completes. | |
| A value that indicates whether the asynchronous operation completed on the thread used to call BeginOperationName instead of completing on a separate ThreadPool thread. | |
| A value that indicates whether the asynchronous operation has completed. |
A BeginOperationName method takes any parameters declared in the signature of the synchronous version of the method that are passed by value or by reference. Any out parameters are not part of the BeginOperationName method signature. The BeginOperationName method signature also includes two additional parameters. The first of these defines an AsyncCallback delegate that references a method that is called when the asynchronous operation completes. The caller can specify null (Nothing in Visual Basic) if it does not want a method invoked when the operation completes. The second additional parameter is a user-defined object. This object can be used to pass application-specific state information to the method invoked when the asynchronous operation completes. If a BeginOperationName method takes additional operation-specific parameters, such as a byte array to store bytes read from a file, the AsyncCallback and application state object are the last parameters in the BeginOperationName method signature.
BeginOperationName returns control to the calling thread immediately. If the BeginOperationName method throws exceptions, the exceptions are thrown before the asynchronous operation is started. If the BeginOperationName method throws exceptions, the callback method is not invoked.
Ending an Asynchronous Operation
The EndOperationName method ends asynchronous operation OperationName. The return value of the EndOperationName method is the same type returned by its synchronous counterpart and is specific to the asynchronous operation. For example, the EndRead method returns the number of bytes read from a FileStream and the EndGetHostByName method returns an IPHostEntry object that contains information about a host computer. The EndOperationName method takes any out or ref parameters declared in the signature of the synchronous version of the method. In addition to the parameters from the synchronous method, the EndOperationName method also includes an IAsyncResult parameter. Callers must pass the instance returned by the corresponding call to BeginOperationName.
If the asynchronous operation represented by the IAsyncResult object has not completed when EndOperationName is called, EndOperationName blocks the calling thread until the asynchronous operation is complete. Exceptions thrown by the asynchronous operation are thrown from the EndOperationName method. The effect of calling the EndOperationName method multiple times with the same IAsyncResult is not defined. Likewise, calling the EndOperationName method with an IAsyncResult that was not returned by the related Begin method is also not defined.
Note |
|---|
| For either of the undefined scenarios, implementers should consider throwing InvalidOperationException. |
Note |
|---|
| Implementers of this design pattern should notify the caller that the asynchronous operation completed by setting IsCompleted to true, calling the asynchronous callback method (if one was specified) and signaling the AsyncWaitHandle. |
Application developers have several design choices for accessing the results of the asynchronous operation. The correct choice depends on whether the application has instructions that can execute while the operation completes. If an application cannot perform any additional work until it receives the results of the asynchronous operation, the application must block until the results are available. To block until an asynchronous operation completes, you can use one of the following approaches:
-
Call EndOperationName from the application’s main thread, blocking application execution until the operation is complete. For an example that illustrates this technique, see Blocking Application Execution by Ending an Asynchronous Operation.
-
Use the AsyncWaitHandle to block application execution until one or more operations are complete. For an example that illustrates this technique, see Blocking Application Execution Using an AsyncWaitHandle.
Applications that do not need to block while the asynchronous operation completes can use one of the following approaches:
-
Poll for operation completion status by checking the IsCompleted property periodically and calling EndOperationName when the operation is complete. For an example that illustrates this technique, see Polling for the Status of an Asynchronous Operation.
-
Use an AsyncCallback delegate to specify a method to be invoked when the operation is complete. For an example that illustrates this technique, see Using an AsyncCallback Delegate to End an Asynchronous Operation.
See Also
The Asynchronous Programming Model (APM), as implemented by Delegates, consists of three parts: BeginInvoke, EndInvoke, and Rendezvous techniques. BeginInvoke starts an algorithm, impelmented via a method, on a new thread. EndInvoke retrieves the result of that method. The Rendezvous techniques allow you to determine when the asynchronous operation has completed.
There are three different types of Rendezvous techniques you can use to retrieve the results of an asynchronous delegate invocation. The first is Wait-Till-Completion, implemented via EndInvoke. Calling this method will block the current thread until the results of the asynchronous method are available. This is the least effective method, as it virtually eliminates the benefits of APM.
private delegate string StringReturningDelegate(); // a delegate for a method that takes no params and returns a string
private void Main()
{
// create an instance of the delegate pointing to a method that takes ten seconds to complete
StringReturningDelegate fd = new StringReturningDelegate(MethodThatTakes10SecondsToComplete);
// Begin invocation of this delegate
IAsyncResult receipt = fd.BeginInvoke(null, null);
// Immediately call EndInvoke, which will block for, oh, say, right about ten seconds
string result = fd.EndInvoke(receipt);
Console.Write(result);
Console.Read();
}
// A method that takes 10 seconds, then returns a string
private string MethodThatTakes10SecondsToComplete() { Thread.Sleep(10000); return "Done!"; }
This code segment demonstrates Wait-Till-Completion. You can see that it offers no benefits over calling MethodThatTakes10SecondsToComplete synchronously, as the EndInvoke method will block the calling thread.
The second Rendezvous technique is called Polling. In this technique, you check a property of the IAsyncResult object called IsCompleted. This property will return false until the async operation has completed. The following code segment demonstrates polling on the same method:
private delegate string StringReturningDelegate(); // a delegate for a method that takes no params and returns a string
private void Main()
{
// create an instance of the delegate pointing to a method that takes ten seconds to complete
StringReturningDelegate fd = new StringReturningDelegate(MethodThatTakes10SecondsToComplete);
// Begin invocation of this delegate
IAsyncResult receipt = fd.BeginInvoke(null, null);
// Poll IsCompleted until it returns true; Sleep the current thread between checks to reduce CPU usage
while(!receipt.IsCompleted)
Thread.Sleep(10);
string result = fd.EndInvoke(receipt);
Console.Write(result);
Console.Read();
}
// A method that takes 10 seconds, then returns a string
private string MethodThatTakes10SecondsToComplete() { Thread.Sleep(10000); return "Done!"; }
This method isn't much better. You sleep away all the extra time that you could have been productive with (like in college).
The third, and most efficient, Rendezvous technique is Method Callback. In this technique, you pass a delegate to the BeginInvoke method that will be called when the asynchronous operation has completed. It will not block your execution, or waste any CPU cycles. You should always use this method of Rendezvous.
private delegate string StringReturningDelegate(); // a delegate for a method that takes no params and returns a string
private void Main()
{
// create an instance of the delegate pointing to a method that takes ten seconds to complete
StringReturningDelegate fd = new StringReturningDelegate(MethodThatTakes10SecondsToComplete);
// Begin invocation of this delegate
fd.BeginInvoke(AsyncOpComplete, null);
// Do tons of work here. No, seriously.
Console.Read();
}
/// <summary>
/// Retrieves the results of MethodThatTakes10SecondsToComplete when called asynchronously
/// </summary>
/// <param name="receipt">The IAsyncResult receipt.</param>
private void AsyncOpComplete(IAsyncResult receipt)
{
// Cast to the actual object so that we can access the delegate
AsyncResult result = (AsyncResult)receipt;
// retrieve the calling delegate
StringReturningDelegate gsld = (StringReturningDelegate)result.AsyncDelegate;
// Retrieve our results; this is guaranteed not to block, as the async op is complete
string result = gsld.EndInvoke(receipt);
// write the result to the console
Console.Write(result);
}
// A method that takes 10 seconds, then returns a string
private string MethodThatTakes10SecondsToComplete() { Thread.Sleep(10000); return "Done!"; }
This method allows the program to continue execution while the async operation completes on another thread. When the operation completes, the delegate will call the method AsyncOpComplete, which was passed to the delegate via the BeginInvoke method.
Take note that the implementation of MethodThatTakes10SecondsToComplete has not changed. This is the major strength of the APM. You create the method you wish to call asynchronously just as you would if it was to be used synchronously. All the work required to call this method asynchronously is performed by the delegate. All you have to do is create a delegate that matches the signature of your method, and construct a method (that returns void and takes one IAsyncResult parameter) designed to be run upon completion of the async operation.
Note