Many important aspects of the .NET framework support asynchronous programming. You can interact asynchronously with File IO, HTTP, TCP, XML Web Services, ASP.NET Web Forms, and message queues with MSMQ Microsoft's Message Queue. The asynchronous invocation works the same whether you are using File IO, XML Web Services, or whatever.
Two examples in the subsections that follow demonstrate how to asynchronously invoke an XML Web Service and use File IO streams.
Invoking XML Web Services Asynchronously
Chapter 13, Creating Web Services, and Chapter 14, Advanced Web Services, thoroughly cover Web Services. I don't want to repeat all that information here, so for now I'll just give you the broad strokes of asynchronous Web Services.
A Web Service is a DLL that allows you to invoke methods across the Internet. The data requested is returned as text in a special format referred to as XML . (Remember, we are just broadly covering the basics here.)
When you find a Web Service you want to use, you will need to rely on Web Services Description Language (WSDL, pronounced whiz-dal ). In short, the wsdl.exe utility application uses the CodeDOM to generate a proxy class on your client machine. The proxy class creates proxy types for types exported by the Web Service and adds proxy methods to simplify invoking the Web Service methods. One form of the proxy methods is a synchronous method and the other form represents asynchronous proxy methods.
Assume you have a Web Service that exports the Calculate method. The wsdl.exe utility application would generate a proxy class that implements a Calculate method and a pair of methods, BeginCalculate and EndCalculate . Call BeginCalculate to invoke the Web Service method asynchronously and EndCalculate when the data is ready. The Web Service proxy class inherits from SoapHttpClientProtocol . The proxy methods call the SoapHttpClientProtocol.BeginInvoke and SoapHttpClientProtocol.EndInvoke methods inherited in the proxy class.
Now that we have this information under our belts, all we have to do is apply the information we already know about invoking methods asynchronously. The code in Listing 6.7 assumes that we have created a Windows client application and added a Web Reference to a Web Service containing the Calculate method exported by the Web Service.
Listing 6.7 Creating an Instance of a Proxy Class and Invoking a Method Asynchronously
1: Public Class Form1 2: Inherits System.Windows.Forms.Form 3: 4: [ Windows Form Designer generated code ] 5: 6: 7: Private Service As localhost.Service1 = New localhost.Service1() 8: 9: Private Sub Calculate(ByVal Numerator As Long, _ 10: ByVal Denominator As Long) 11: 12: Service.BeginCalculate(Numerator, Denominator, _ 13: AddressOf Callback, Nothing) 14: 15: End Sub 16: 17: Private Sub Callback(ByVal Result As IAsyncResult) 18: 19: Dim Value As Long = Service.EndCalculate(Result) 20: MsgBox("Result=" & Value) 21: 22: End Sub 23: 24: Private Sub Button1_Click(ByVal sender As System.Object, _ 25: ByVal e As System.EventArgs) Handles Button1.Click 26: 27: Calculate(Convert.ToInt64(TextBox1.Text), _ 28: Convert.ToInt64(TextBox2.Text)) 29: 30: End Sub 31: End Class
Line 7 demonstrates how to create an instance of the Web Service proxy class. The name localhost represents the namespace of the proxy class, which reflects the host name of the computer owning the Web Service. The example Web Service resides on my laptop, hence localhost . Line 7 is no different than the code needed to create any object.
Lines 12 and 13 demonstrate how to invoke the Web Service asynchronously. The first two arguments are those needed by the Calculate method, and the third argument is the callback method. When the data is ready, the Web Service invokes the callback method. In Listing 6.7 the callback method is named Callback .
The basic idea is still the same. Call the asynchronous Begin method and use one of the polling completion techniques described in the Completing Asynchronous Calls subsection earlier in this chapter.
Invoking File Operations Asynchronously
The System.IO.Stream class is the base class for several IO classes. Defined in the Stream base class are methods for invoking IO operations asynchronously. The convention used for asynchronous methods is to prefix asynchronous methods with Begin and End . For example, the Stream classes implement the methods Read and Write for synchronous IO operations and BeginRead and EndRead and BeginWrite and EndWrite for asynchronous operations. Listing 6.8 demonstrates an asynchronous file read.
Listing 6.8 An Asynchronous Read Using the FileStream Class
1: Imports System.IO 2: 3: Public Class Form1 4: Inherits System.Windows.Forms.Form 5: 6: [ Windows Form Designer generated code ] 7: 8: Private Stream As FileStream = Nothing 9: Private Data() As Byte 10: 11: Private Sub Button1_Click(ByVal sender As System.Object, _ 12: ByVal e As System.EventArgs) Handles Button1.Click 13: 14: If Not (Stream Is Nothing) Then Return 15: Stream = New FileStream("..\AsyncFileIO.sln", FileMode.Open) 16: 17: ReDim Data(Stream.Length) 18: Stream.BeginRead(Data, 0, Stream.Length, _ 19: AddressOf Finished, Nothing) 20: End Sub 21: 22: Private Sub Finished(ByVal Result As IAsyncResult) 23: 24: Dim BytesRead = Stream.EndRead(Result) 25: TextBox1.Text = System.Text.ASCIIEncoding.ASCII.GetString(Data) 26: Stream.Close() 27: Stream = Nothing 28: End Sub 29: 30: End Class
Again, as we have seen throughout this section, we start the asynchronous operation with a Begin method and obtain the actual data in the callback method with the End method. BeginRead is shown in line 18 and EndRead in line 24.
We need to be careful with files as we cannot open an unlimited number of files without closing files, and other users will not be able to use a file that we are using. Listing 6.8 demonstrates how we close FileStream (line 26) after we have finished with the data. The only minor difficulty we have here is that we might not get all the data in one shot. If the number of bytes read doesn't equal the total number of bytes requested, we need to go back to the well and request more bytes. You have to solve this problem whether you read the file synchronously or asynchronously.
As an exercise, modify the code comparing the bytes actually read to those requested and call BeginRead until all the bytes have been read. Experiment with large files to test your solution. (Hint: Use a loop that totals the bytes read and calls BeginInvoke from the callback until the total bytes equals the number of bytes in the file.)
Use the Timer control for very lightweight background tasks . Use the Timer class in the System.Threading namespace when you need light weight background processing in non-Windows Forms applications. Employ the asynchronous model when you need out-of-order processing, that is, when you need an extra worker thread. You also have two more options. You can use multithreading in the ThreadPool class or spin up your own Thread object. We'll continue our discussion by exploring multithreading.