Creating a Multithreaded Application

In most of this book, the examples have been as simplistic as possible to focus on the new technology. In Chapter 3, you saw a very simple example of a multithreaded application that allowed you to place a long-running process on a separate thread so that the user interface continued to respond to users. Now, however, you will see a more complex example, and it will build on the XML Web services example from Chapter 9, “Building Web Services with VB .NET.” If you have not gone through Chapter 9, you will want to build the service mentioned in the “Building Data-Driven Web Services” section. The XML Web service you built in that section of Chapter 9 works just fine, but it can be slow. Your client application is frozen during the time it takes to retrieve the data and fill the grid. Now, you will work on rewriting that application to make it multithreaded. This example will attempt to keep the code at a minimum, while showing you as much about handling multithreaded applications as possible.

Open the OrderTracker application you created in Chapter 9. This is the client application that calls the OrderInfo Web service. The GetOrders method returns orders from the Northwind database in an ADO.NET dataset. You then bind this dataset to the DataGrid control and display the data.

After the application is open, you will want to make a number of modifications. Add two additional buttons to the form. In Chapter 9, you left the first button named Button1, but now you will modify the Name and Text properties for the three buttons. Make the following modifications to the buttons:

  • Button1: Change the Name property to cmdGetData and the Text property to Load Data.

  • Button2: Change the Name property to cmdBeep and the Text property to Beep.

  • Button3: Change the Name property to cmdLoadGrid and the Text property to Load Grid.

Now, you will make major modifications to the code. Go into the code window, and replace any existing code with the following code, except you want to leave the “Windows Form Designer generated code” section as it exists:

Public Class Form1
   Inherits System.Windows.Forms.Form
   Dim myDS As New System.Data.DataSet()

#Region " Windows Form Designer generated code "

   Private Sub cmdGetData_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles cmdGetData.Click
      GetData()
   End Sub

   Private Sub cmdBeep_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles cmdBeep.Click
      Beep()
   End Sub

   Private Sub GetData()
      Dim myOrders As New Orders()
      myDS = myOrders.GetOrders
      Beep()
   End Sub

   Private Sub cmdLoadGrid_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles cmdLoadGrid.Click
      If myDS.Tables.Count > 0 Then
         DataGrid1.DataSource = myDS
         'the DataMember will be whatever table you saw
         'when you created the dataset
         DataGrid1.DataMember = "Customers"
      End If
   End Sub
End Class

If you look at the code, you'll notice that one of the first things you do is define an ADO.NET DataSet named myDS. The reason for defining this at the module level will become obvious in the next section, when you will read about returning data from the procedures on another thread.

Inside the code for the cmdGetData button, you simply call the GetData sub that you create, and the GetData sub instantiates an instance of the Orders XML Web service. You then call the GetOrders method on Orders, and put the result in the module-level myDS that you created earlier.

The code inside the cmdBeep event handler is very simple—it just issues a beep. You'll use this to test when the main thread of the form is blocked and when it is not.

The code inside cmdLoadGrid first checks whether myDS has any DataTables in it. This keeps you from getting an error if you click the button before you have clicked the GetData button. Next, the code binds the DataGrid to the myDS DataSet, and then the table within the DataSet. As you were warned in Chapter 9, make sure that you use the table name displayed to you when you created the DataAdapter. Even though the query is returning records from five tables in SQL Server, you are returning them all into one flat, table-like structure, and not multiple DataTable objects.

Go ahead and run the project. After the form loads, click the Beep button to make sure you hear the beep. Now, click the Get Data button and immediately click the Beep button again; you will not hear a beep from the Beep button this time. This is, of course, because the long-running (in computer terms) GetData process blocks the execution of the main thread. When the data is returned, you will hear a beep because there is one in the GetData sub. You can then click the Load Grid button to load the data into the grid. Your application should look something like Figure 11.1.

Figure 11.1. Your new client application calling an XML Web service and loading data into a grid.


So far, you might be thinking this isn't anything great, and wondering just where all the multithreading is supposed to happen. In fact, the next step will be to spawn the GetData sub on its own thread, so that your user interface is no longer blocked while the data is being retrieved.

Making the OrderTracker Application Multithreaded

In your application, pressing the Beep button while the XML Web service is being called doesn't work because the action is blocking the thread. You might want to place that functionality on another thread so that the user interface continues to respond while the data is being retrieved. To accomplish this, you need to use the System.Threading.Thread class. The Thread class can be used to create a new thread, on which you can launch some processing.

The GetData sub that you created on the client is used to instantiate a variable of type Orders, where Orders is the XML Web service. You then call the GetOrders method of the Orders object to retrieve the data. The process of instantiating the Orders object and calling the GetOrders method can be time-consuming, and is therefore a good candidate to place on its own thread.

One of the nice things about multithreading is that, in its simplest form, your changes are made in the calling routine, not the called routine. This means that the called routine doesn't actually know it's running on a separate thread from the client that called it. Instead, it just operates normally.

The calling routine, on the other hand, requires some changes in order to process the called routine on a separate thread. The change requires you to instantiate a Thread object, using the Thread class in the System.Threading namespace. When you instantiate a thread, a required parameter must be passed into the constructor of the Thread class. That parameter is a pointer to the function (or sub) that will run on the thread you are creating.

Before you worry about VB .NET not supporting pointers, remember that VB .NET includes support for delegates, which are, in essence, function pointers. A delegate can represent the memory address for a function, so it acts as a pointer in this case. There is an easy way to create a delegate in VB .NET: use the AddressOf operator. AddressOf creates a function delegate that the Thread class then uses to run that function on the new thread. Your syntax looks something like this:

Imports System.Threading
...
Dim myThread As New Thread(AddressOf CalledFunction)

You now have created a new thread, called myThread, which points to the CalledFunction routine. However, creating the thread does not automatically call the CalledFunction routine; instead, you now need to initiate the call to CalledFunction using the thread. You do this by starting the thread, using the Thread.Start method, as shown here:

myThread.Start()

Starting the thread makes the call to this new thread. To test all this, you will make some changes to your code. First, you will change your call from the cmdGetData_Click sub to create a new thread, similar to what was shown earlier. Second, you will change the called routine, GetData, to display a message box instead of beeping when it is finished. The reason is that you will now be able to click the Beep button while the GetData routine is running, so you want something other than a beep to indicate that you are finished retrieving the data.

Change your cmdGetData_Click sub to look like this:

Private Sub cmdGetData_Click(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles cmdGetData.Click
   Dim threadGetData As New System.Threading.Thread(AddressOf GetData)
   threadGetData.Start()
End Sub

Notice that you could have added Imports System.Threading to the top to simplify reference to the Thread class.

Now, modify your GetData sub to look like this:

Private Sub GetData()
   Dim myOrders As New Orders()
   myDS = myOrders.GetOrders
   MsgBox("Done")
End Sub

Now, run the application again. After it starts, click on the Get Data button. Then click on the Beep button. You will hear the beep immediately because the data is being retrieved on a separate thread. The CPU is giving time slices to both threads, so your user interface is still able to respond to events.

When the GetData sub is done, you will get a message box indicating that it is finished. However, unlike what you are most likely used to in Windows applications, you can click on the Beep button without clearing the message box! This is because the message box is running on a different thread, so it is not blocking the execution of the user interface.

Go ahead and click the OK button on the message box to close it. The Load Data button still works as it did before, and loads the ADO.NET dataset into the grid. This example demonstrates that you can take some of the long-running parts of your application and move them to other threads so that people can continue to use your application while those processes are running. In this example, there wasn't actually anything else for the person to do, but you might well have applications that need to parse or search through large files, and that can be done in the background while the person does other things.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.205.165