The asynchronous programming model (APM) is an asynchronous operation that uses the IAsyncResult
interface as its design pattern. It's also called the IAsyncResult
pattern. For this purpose, the framework has provided the method named BeginXx
and EndXx
, in which Xx
is the operation name, for instance, BeginRead
and EndRead
provided by the FileStream
class to read bytes from a file asynchronously.
The difference in the synchronous Read()
method with BeginRead()
and EndRead()
can be recognized from the method's declaration, as follows:
public int Read( byte[] array, int offset, int count ) public IAsyncResult BeginRead( byte[] array, int offset, int numBytes, AsyncCallback userCallback, object stateObject ) public int EndRead( IAsyncResult asyncResult )
As we can see, in the synchronous Read()
method, we need three parameters; they are array
, offset
, and numBytes
. In the BeginRead()
method, there are two more parameter additions; they are userCallback
, the method that will be called when the asynchronous read operation is completed, and stateObject
, an object provided by the user that distinguishes the asynchronous read request from other requests.
Now, let's take a look at the following code, which we can find in the APM.csproj
project, in order to distinguish the asynchronous BeginRead()
method from the synchronous Read()
method in a clearer way:
public partial class Program { public static void ReadFile() { FileStream fs = File.OpenRead( @"......LoremIpsum.txt"); byte[] buffer = new byte[fs.Length]; int totalBytes = fs.Read(buffer, 0, (int)fs.Length); Console.WriteLine("Read {0} bytes.", totalBytes); fs.Dispose(); } }
The preceding code will synchronously read the LoremIpsum.txt
file (included in the APM.csproj
project), which means that the reading process has to be completed before executing the next process. If we run the preceding ReadFile()
method, we will get the following output on the console:
Now, let's compare the synchronous reading process using the Read()
method with the asynchronous reading process using the BeginRead()
and EndRead()
methods from the following code:
public partial class Program { public static void ReadAsyncFile() { FileStream fs = File.OpenRead( @"......LoremIpsum.txt"); byte[] buffer = new byte[fs.Length]; IAsyncResult result = fs.BeginRead(buffer, 0, (int)fs.Length, OnReadComplete, fs); //do other work while file is read int i = 0; do { Console.WriteLine("Timer Counter: {0}", ++i); } while (!result.IsCompleted); fs.Dispose(); } private static void OnReadComplete(IAsyncResult result) { FileStream fStream = (FileStream)result.AsyncState; int totalBytes = fStream.EndRead(result); Console.WriteLine("Read {0} bytes.", totalBytes);fStream.Dispose(); } }
As we can see, we have two methods named ReadAsyncFile()
and OnReadComplete()
. The ReadAsyncFile()
method will read the LoremIpsum.txt
file asynchronously and then invoke the OnReadComplete()
method just after finishing reading the file. We have additional code to make sure that the asynchronous operation runs properly using the following do-while
looping code snippet:
//do other work while file is read int i = 0; do { Console.WriteLine("Timer Counter: {0}", ++i); } while (!result.IsCompleted);
The preceding do-while
loop will iterate until the asynchronous operation is completed, as indicated in the IsComplete
property of IAsyncResult
. The asynchronous operation is started when the BeginRead()
method is invoked, as shown in the following code snippet:
IAsyncResult result = fs.BeginRead( buffer, 0, (int)fs.Length, OnReadComplete, fs);
After that, it will continue with the next process while it reads the file. The OnReadComplete()
method will be invoked when the reading process is finished, and since the implementation of the OnReadComplete()
method set the IsFinish
variable to true, it will stop our do-while
looping.
The output we will get by running the ReadAsyncFile()
method is as follows:
From the screenshot of the preceding output, we can see that the iteration of the do-while
loop is successfully executed when the reading process is run as well. The reading process is finished in the 64th iteration of the do-while
loop.
We can also use LINQ to define the OnReadComplete()
method so that we can replace that method using the anonymous method, as follows:
public partial class Program { public static void ReadAsyncFileAnonymousMethod() { FileStream fs = File.OpenRead( @"......LoremIpsum.txt"); byte[] buffer = new byte[fs.Length]; IAsyncResult result = fs.BeginRead(buffer, 0, (int)fs.Length, asyncResult => { int totalBytes = fs.EndRead(asyncResult); Console.WriteLine("Read {0} bytes.", totalBytes); }, null); //do other work while file is read int i = 0; do { Console.WriteLine("Timer Counter: {0}", ++i); } while (!result.IsCompleted); fs.Dispose(); } }
As we can see, we replace the invocation of the BeginRead()
method with the following code snippet:
IAsyncResult result = fs.BeginRead( buffer, 0, (int)fs.Length, asyncResult => { int totalBytes = fs.EndRead(asyncResult); Console.WriteLine("Read {0} bytes.", totalBytes); }, null);
From the preceding code, we can see that we don't have the OnReadComplete()
method anymore since it has been represented by the anonymous method. We remove the FileStream
instance in the callback because the anonymous method in lambda will access it using the closure. And if we invoke the ReadAsyncFileAnonymousMethod()
method, we will get the exact same output as the ReadAsyncFile()
method except the iteration count, since it depends on the CPU speed.
Besides the IsCompleted
property, which is used to fetch the value that indicates whether the asynchronous operation is complete, there are three more properties we can use when dealing with IAsyncResult
; they are as follows:
AsyncState
: This is used to retrieve an object defined by the user that qualifies or contains information about an asynchronous operationAsyncWaitHandle
: This is used to retrieve WaitHandle
(an object from the operating system that waits for exclusive access to shared resources), which indicates the completeness of the asynchronous operationCompletedSynchronously
: This is used to retrieve a value that indicates whether the asynchronous operation completed synchronouslyUnfortunately, there are several shortages when applying APM, such as the inability to do a cancelation. This means that we cannot cancel the asynchronous operator because from the invocation of BeginRead
until the callback is triggered, there is no way to cancel the background process. If LoremIpsum.txt
is a gigabyte file, we have to wait until the asynchronous operation is finished instead of canceling the operation.