There are two ways to get your laundry done. The first way is to put your laundry into the machine, put in a few quarters and then wait for the machine to run. You wait. And then you wait. About 30 minutes later the machine stops and you take your laundry back.
The second way to get your laundry done is to take it to the Laundromat and say "Here, please clean this clothing and call me back when you are done. Here's my cell number." The person in the Laundromat does the work while you go off and do something else. When your laundry is done, they call you and say "Your clothes are clean. Your pick up number is 123." When you return, you give the person at the desk the number 123, and you get back your clean clothes.
The .NET Framework supports the notion of a "callback." (Callbacks have been in use for many years, and Windows programmers have been using callbacks at least since Win 3.x.) The idea of a callback is that you say to a method, "Do this work, and call me back when you are done." It is a simple and clean mechanism for multitasking.
The .NET Framework provides a class, FileStream, which provides asynchronous reading of a file. You do not have to create the threads yourself; FileStream will read the file for you asynchronously, and callback a method you designate when it has data for you, as illustrated in Example 12-4.
Option Strict On Imports System Imports System.IO Imports System.Text Public Class AsynchIOTester Private inputStream As Stream ' delegated method Private myCallBack As AsyncCallback ' buffer to hold the read data Private buffer( ) As Byte ' the size of the buffer Private Const BufferSize As Integer = 256 ' constructor Sub New( ) ' open the input stream inputStream = New FileStream( _ "C:\temp\streams.txt", _ FileMode.Open, _ FileAccess.Read, _ FileShare.ReadWrite, _ 1024, _ True) ' allocate a buffer buffer = New Byte(BufferSize) {} ' assign the call back ' myCallBack = New AsyncCallback(AddressOf OnCompletedRead) myCallBack = AddressOf OnCompletedRead End Sub 'New Public Shared Sub Main( ) ' create an instance of AsynchIOTester ' which invokes the constructor Dim theApp As New AsynchIOTester( ) ' call the instance method theApp.Run( ) End Sub 'Main Sub Run( ) inputStream.BeginRead( _ buffer, _ 0, _ buffer.Length, _ myCallBack, _ Nothing) Dim i As Long For i = 0 To 499999 If i Mod 1000 = 0 Then Console.WriteLine("i: {0}", i) End If Next i End Sub 'Run ' call back method Sub OnCompletedRead(ByVal asyncResult As IAsyncResult) Dim bytesRead As Integer = inputStream.EndRead(asyncResult) ' if we got bytes, make them a string ' and display them, then start up again. ' Otherwise, we're done. If bytesRead > 0 Then Dim s As String = _ Encoding.ASCII.GetString(buffer, 0, bytesRead) Console.WriteLine(s) inputStream.BeginRead( _ buffer, 0, buffer.Length, myCallBack, Nothing) End If End Sub 'OnCompletedRead End class
In Example 12-4, you open a FileStream object, passing in the (hardwired) name of the file, the fileMode (e.g., Open), the FileAccess flag (e.g., Read), and the FileShare mode (e.g., ReadWrite). You also pass in an integer signifying the buffer size and a Boolean indicating whether the FileStream should be opened asynchronously:
inputStream = New FileStream( _ "C:\temp\streams.txt", _ FileMode.Open, _ FileAccess.Read, _ FileShare.ReadWrite, _ 1024, _ True)
The FileStream object provides a method, BeginRead( ), to provide asynchronous reading of the file (reading a block of text into memory while your other code does its work). You must pass in a buffer in which it will place your data, along with the offset into that buffer into which it will begin reading. You pass in the length of the buffer and you must tell BeginRead( ) the method you want to call back to.
You designate the method you want to call back to by passing in a delegate. You'll create that delegate in the next example as a member of your class:
Private myCallBack As AsyncCallback
The type of the delegate was determined by the author of the FileStream class, which designated that you must pass in a Delegate of type AsyncCallback. The AsyncCallback delegate is defined in the documentation as follows:
Public Delegate Sub AsyncCallback( _ ByVal ar As IAsyncResult)
That is, it is a subroutine (and thus returns no value) and takes as its single parameter an object that implements the interface IAsyncResult. You do not have to implement that class yourself. All you need to do is create a method that declares a parameter of type IAsyncResult. Such an object will be passed to you by the FileStream's BeginRead( ) method, and you will use it as a token that you will return to the FileStream by calling EndRead( ). Here is the declaration of the OnCompletedRead( ) method, which you'll encapsulate in your AsyncCallback delegate:
Sub OnCompletedRead(ByVal asyncResult As IAsyncResult) '... End Sub 'OnCompletedRead
You instantiate the delegate in the constructor to your class:
myCallBack = New AsyncCallback(AddressOf OnCompletedRead)
|
You are now ready to start the callback process. You begin in your test class's Run( ) method by calling BeginRead( ):
Sub Run( ) inputStream.BeginRead( _ buffer, _ 0, _ buffer.Length, _ myCallBack, _ Nothing)
The first parameter is a buffer, declared in this case as a member variable:
Private buffer( ) As Byte
The second parameter (0) is the offset into that buffer. By entering 0, the data read from the disk will be written to the buffer starting at offset 0. The third parameter is the length of the buffer. The fourth parameter is the one we care about: the AsyncCallBack delegate you declared and instantiated earlier. The fifth and final parameter is a state object. The state object can be any object you want; typically it is used to hold the current state of your calling object. In the case shown, you pass Nothing, a VB.NET keyword that indicates that you have no state object.
After you call BeginRead( ), you go on with your other work. In Example 12-4, that work is simulated by counting to half a million:
Dim i As Long For i = 0 To 499999 If i Mod 1000 = 0 Then Console.WriteLine("i: {0}", i) End If Next i
The FileStream will go off and open the file on your behalf. It will then read from the file and fill your buffer. When it is ready for you to process the data, it will interrupt your work in Run( ), and will call the method you encapsulated with the delegate. You will remember that the delegated method is called OnCompletedRead( ):
Sub OnCompletedRead(ByVal asyncResult As IAsyncResult) Dim bytesRead As Integer = inputStream.EndRead(asyncResult) ' if we got bytes, make them a string ' and display them, then start up again. ' Otherwise, we're done. If bytesRead > 0 Then Dim s As String = Encoding.ASCII.GetString(buffer, 0, bytesRead) Console.WriteLine(s) inputStream.BeginRead(buffer, 0, buffer.Length, myCallBack, Nothing) End If
When the FileStream calls your method, it will pass in an instance of a class that implements the IAsyncResult interface. The first thing you do in this method is pass that IAsyncResult object to the FileStream's EndRead( ) method. EndRead( ) returns an integer indicating the number of bytes successfully read from the file. If that value is greater than zero, your buffer has data in it.
The buffer is a buffer of bytes, but you need a string to display. To convert the buffer to a string, you will call Encoding.ASCII.GetString( )—a shared method that will take your buffer, an offset, and the number of bytes read and return an ASCII string. You can then display that string to the console.
Finally, you'll call BeginRead( ) again, passing back the buffer, the offset (again 0), the length of the buffer, and the delegate, as well as Nothing for the state object. This begins another round. Control will return to the Run( ) method, and you will continue counting.
The effect is that you ping-pong back and forth between the work you are doing in Run( ) (counting to 500,000) and the work you are doing in OnCompletedRead( ). You have achieved multitasking without instantiating or managing any threads; you have only to write the callback mechanism and let the FileStream do the thread management for you.
Top |