The Asynchronous Programming Model (APM) is one of the oldest patterns introduced by Microsoft in .NET 1.0 back in 2001 for asynchronous programming handling.
The pattern is easy. To start a deferred job, you simply start such a job by using a
Delegate (remote method invoker) and then get an object back of type IAsyncResult
to know the status of such a remote operation. Here an asynchronous programmed application to compute file hashes. The application will add a ".
" to the Starting data computation
initial message to acknowledge to the user that the application is still processing. The following examples use the blocking approach:
static void Main(string[] args) { //a container for data var complexData = new byte[1024]; //a delegate object that gives us the ability to trigger the pointed method in async way var dataDelegate = new Action<byte[]>(ComputeComplexData); Console.Write("Starting data computation..."); //start retrieving complex data in another thread IAsyncResult dataStatus = dataDelegate.BeginInvoke(complexData, null, null); //waiting the completation while (!dataStatus.IsCompleted) { Console.Write("."); Thread.Sleep(100); } Console.WriteLine(" OK"); //instantiate a delegate for hash elaboration in async way var hashDelegate = new Func<byte[], string>(ComputeHash64); Console.Write("Starting hash computation..."); IAsyncResult hashStatus = hashDelegate.BeginInvoke(complexData, null, null); //waiting the completion while (!hashStatus.IsCompleted) { Console.Write("."); Thread.Sleep(100); } //this time the async operation returns a value //we need to use the delegate again to catch this value from the other thread var hash = hashDelegate.EndInvoke(hashStatus); Console.WriteLine(" OK"); Console.WriteLine("END"); Console.ReadLine(); } static void ComputeComplexData(byte[] data) { var r = new Random(); Thread.Sleep(3000); r.NextBytes(data); } public static string ComputeHash64(byte[] data) { using (var engine = new System.Security.Cryptography.MD5CryptoServiceProvider()) { Thread.Sleep(3000); var hash = engine.ComputeHash(data); return Convert.ToBase64String(hash); } }
The code is very easy. The class that helps make things asynchronous here is the Delegate
class. Here, we use the pre-generated versions, Action<T>
and Func<T>
, compliant with the generic pattern, which helps us use any feature required of such Delegate
objects without having to declare a specific one each time.
A Delegate
object is an object-oriented method pointer with a lot of added features such as asynchronous support and multiple method handlers. Any CLR event is a Delegate too. Such a class gives us the ability to invoke any remote method in a synchronous way (with the usual .Invoke
method), or in an asynchronous way with BeginInvoke
/EndInvoke
, as visible in the preceding example. As mentioned earlier, the IsCompleted
property gives us feedback about the remote execution completion of all pointed remote methods.
The usage of such a blocking asynchronous operation, without the need to block the execution of the main thread, helps create respondent UXs as a download popup, or special import/export features.
There are a lot of SDKs that give us the ability to use APM patterns like in the preceding example. In .NET 4.0, many core-framework APM implementations have been updated to the new TAP framework (discussed later in this chapter in the Task-based Asynchronous Pattern (TAP) section). Here's another APM example showing network communication with the IDisposable
/using
pattern:
static void Main(string[] args) { //running in .NET 4.0 var url = "http://www.google.com"; Console.Write("Asking for {0}", url); //create a new web request for given url var request = WebRequest.Create(url); //start collecting response in async way var responseStatus = request.BeginGetResponse(null, null); //waiting the completation while (!responseStatus.IsCompleted) { Console.Write("."); Thread.Sleep(100); } Console.WriteLine(" OK"); //a size counter int size = 0; //catch back on the main thread the response using (var response = request.EndGetResponse(responseStatus)) //open the stream to access the response data using (var stream = response.GetResponseStream()) //open a reader for such data using (var r = new StreamReader(stream, Encoding.UTF8)) //until data is valid while (!r.EndOfStream && r.Read() >= 0) size++; Console.WriteLine("Total size: {0:N1}KB", size / 1024D); Console.WriteLine("END"); Console.ReadLine(); }
In the preceding second code example, we found the ability to use the APM with .NET assemblies. The usage is very straightforward. As visible in the two examples given, the IAsyncResult
type gives us the ability to wait for completion in a polling way by repetitively checking the IsCompleted
property value. The additional ability to wait for such completion with a
WaitHandle
class is interesting, as already seen in the Multithreading synchronization section in Chapter 3, CLR Internals. Here's an example:
//alternative 1: //waiting the completation while (!status.IsCompleted) Thread.Sleep(100); //alternative 2: //waiting the completation with the signaling event lock status.AsyncWaitHandle.WaitOne();
Although the WaitHandle
class based alternative may seem to be more comfortable than the one that uses the polling property check, the difference is that the polling one also gives us the ability to update the UI while waiting. Instead, the WaitHandle
class will simply stop the execution of the thread where such an object is being waited on. Bear in mind that multiple threads can wait together at the same time as the IAsyncResult
completion status or wait handle, without any issues (until this brings some other resources to the race condition).
In addition to the blocking wait, as said at the beginning of the paragraph, we have the ability to catch the completion of an asynchronous method execution, to another asynchronous method by passing a Delegate
object, which represents a callback method. If multiple callbacks are caught in the same handler, a state parameter can help in its handling. Using a callback method gives us a more event-based approach. Consider that the callback executes in the same thread as the asynchronous processing. Here is an example with a single callback method:
static void Main(string[] args) { var invoker = new Func<int>(OnCreateInteger); //trigger 3 invocations sending the same invoker as the state to the ending handler invoker.BeginInvoke(OnHandleInteger, invoker); Thread.Sleep(100); invoker.BeginInvoke(OnHandleInteger, invoker); Thread.Sleep(100); invoker.BeginInvoke(OnHandleInteger, invoker); Console.WriteLine("MAIN THREAD ENDED"); Console.ReadLine(); } private static void OnHandleInteger(IAsyncResult ar) { //the state contains the sent invoker var invoker = (Func<int>)ar.AsyncState; Console.WriteLine("Async operation returned {0}", invoker.EndInvoke(ar)); } private static int OnCreateInteger() { Thread.Sleep(3000); //returns a random integer return DateTime.Now.Millisecond; }
3.147.44.182