Multithreading in Windows Forms

Team-Fly    

 
Visual Basic .NET Unleashed
By Paul Kimmel
Table of Contents
Chapter 14.  Multithreaded Applications


Tip

This section assumes that you have a certain comfort level working with Windows Forms, and delves directly into the topic of multithreading with forms. If you need basic information on how to design and construct Windows Forms, turn to Chapter 15, "Using Windows Forms," for coverage from the ground up.


Windows controls are not thread-safe. You are going to hear this a lot as you begin to explore Visual Basic .NET, participate in user discussions, and read technical materials. All of the implications of this statement remain to be determined except by perhaps a very small minority at Microsoft. I know this from having several discussions with program managers and a few developers, and reading the opinions of other developer-writers.

Note

One of the best discussions on the risks of using threads in VB .NET can be found in Moving to VB.NET: Strategies, Concepts, and Code, Beta 2 (Appleman, 2001). Appleman's approach is somewhat conservativeprobably derived from years of learning the hard way.

Software is difficult enough to write well; throw in multithreading and it can get downright tricky. My approach is somewhat less conservative. Threads exist for a reason, and limitations regarding the CLR specifically , Windows Formsare problems for Microsoft to solve. Other frameworks have breached this threshold, as I am sure Microsoft will.

Know what you are getting into. Use threads sparingly. Anticipate trickier bugs . Don't hesitate to ask for experienced help, and don't blow deadlines striving for anticipated multithreading performance gains. If you are adding threading for performance reasons, consider making the revisions after you have established proof that performance tuning is needed, and ensure that you have a rollback plan in case threads break your application. Potential problems can be further mitigated by writing refactored code, isolating the multithreaded code as much as possible, and having a backup plan in case your threaded code fails catastrophically.


Multithreaded Strategies for Windows Forms

The purpose of using threads with Windows Forms will generally be that you want some action and a visual representation of a result to be displayed on a form. The most common approach to this problem is to write the thread, having the thread update a field that you have added to the form, and then have a timer at regular intervals read the value and update the form.

The reason for this is that you are not directly interacting with Windows Forms across thread boundaries if you are writing to a field you added, and the timer Tick event will be running on the same thread as the form. In essence, the thread updates the data and the timer updates the form.

The second approach that is often mentioned is to use the Control.Invoke method implemented in every control. You can call Control.InvokeRequired first, but when you're working with Windows Forms controls, assume that it is required. Control. Invoke takes a MethodInvoker delegate argument and calls the method referenced by the delegate on the same thread as the form. Listing 15.6 in the "Region Class" section of Chapter 15 demonstrates using the ThreadPool and the Invoke method, combined with the Form.Opacity property to fade in a form.

Both the Timer and the Control.Invoke have worked reliably for me when using threads in conjunction with Windows Forms. The bigger problem has been determining when to allow the user to update the form or control based on completion of the thread or threads. This problem can be resolved using synchronization techniques explained earlier in this chapter.

As you will quickly determine from many of the Quickstarts samples provided by Microsoft, many of the sample threading applications are console applications. However, the form fade-in example in Chapter 15 and the dice example in this chapter are not; they both interact with Windows forms and seem to do so reliably.

Synchronous Calls with Invoke

Windows Forms are based on Win32, which uses the single-threaded apartment model. This means that a window can be created on any thread but cannot switch threads. Consequently, you must call methods of a window on the same thread that the window is on. The CLR supports synchronous calls using Invoke and asynchronous calls using BeginInvoke and EndInvoke. (We'll return to asynchronous calls in a moment.)

You have already used the synchronous Invoke in this chapter. To recap, Invoke takes a MethodInvoker delegate essentially , the AddressOf a subroutine with no parameterscalls the delegate on the same thread as the control calling Invoke and waits for the Invoke method to return. That's the synchronous part.

All Windows Forms controls implement Invoke, BeginInvoke, and EndInvoke because these methods are introduced in the Control class.

Asynchronous Calls with BeginInvoke and EndInvoke

BeginInvoke effectively performs the same task as Invoke but does so asynchronously. That is, BeginInvoke does not sit around and wait for the invoked method to return. As a result, you need another mechanism to respond at a later time when the asynchronous call returns. This is the EndInvoke method.

Calling BeginInvoke

BeginInvoke is introduced in the Control class. BeginInvoke is safe to call from any thread and marshals a call via a delegate onto the thread that the control's handle is on. BeginInvoke is a lightweight means of introducing asynchronous behavior into your Windows Forms-based applications.

BeginInvoke has two versions. The first version takes a delegate and the second (overloaded) version takes a delegate and an array of objects representing the parameters to the delegate method. Because the first parameter in both versions of BeginInvoke takes a System.Delegate, you can define any delegate with any number of parameters and invoke a method asynchronously using methods that match the signature of the delegate. The two signatures for BeginInvoke follow.

 Overloads Public Function BeginInvoke(Delegate) As IAsyncResult Overloads NotOverridable Public Function BeginInvoke(Delegate, _   Object()) As IAsyncResult Implements ISynchronizeInvoke.BeginInvoke 

Although BeginInvoke runs on the same thread as the calling control, it does so asynchronously much the same way any event would run. You get behavior similar to starting a process on a Timer tick. Listing 14.8 demonstrates using the asynchronous behavior of BeginInvoke to initialize a ListBox when an application loads while letting the form's Load event complete.

Listing 14.8 Using BeginInvoke for lightweight asynchronous processing
  1:  Imports System.Threading  2:   3:  Public Class Form1  4:  Inherits System.Windows.Forms.Form  5:   6:  [ Windows Form Designer generated code ]  7:   8:  Private Delegate Sub Invoker(ByVal Count As Integer)  9:   10:  Private Sub LoadListBox(ByVal Count As Integer)  11:  Dim I As Integer  12:  For I = 0 To Count  13:  ListBox1.Items.Add(I)  14:  Next  15:  End Sub  16:   17:  Private Sub Form1_Load(ByVal sender As System.Object, _  18:  ByVal e As System.EventArgs) Handles MyBase.Load  19:   20:  Dim AsynchResult As IAsyncResult  21:  Dim Count As Integer = 100000  22:  AsynchResult = BeginInvoke(New Invoker(AddressOf LoadListBox), _  23:  New Object() {Count})  24:   25:  End Sub  26:   27:  End Class 

This listing simulates a labor- intensive form initialization process by adding 100,000 elements to a ListBox. With synchronous behavior, the ListBox would delay the appearance of the form and completion of the form's Load event. As a result of invoking the ListBox initialization process with BeginInvoke, the ListBox loads asynchronously, allowing the Load event to finish and mitigating the need for a thread for such a simple task.

The delegate Invoker is defined as a subroutine taking a single integer argument. The method LoadListBox matches the signature of the delegate and is used as the first argument for BeginInvoke on line 22. (Notice that the verbose construction of the Invoker delegate was employed.) The second argument to BeginInvoke is an array of objects. The way this array is created and initialized may look a little strange .

The second argument to BeginInvoke demonstrates an inline construction of an Object initialized as an array. The value of Count on line 23 becomes the value passed to the Count parameter in the LoadListBox method. If you need to pass additional arguments to your delegate, add them to the array initializer, delimiting each argument with a comma. The verbose form demonstrating construction of an array of objects initialized by an array of value types follows .

 Dim O() As Object = {Count} Dim O As Object() = {Count} Dim O() As Object = New Object() {Count} Dim O As Object() = New Object() {Count} 

All four versions construct the same object. (You might encounter any of the four types depending on the inclinations of individual programmers.) Versions 1 and 2 declare an array of Object and initialize the array to a single-element array containing the value Count. The first version uses the array descriptor on the variable name and the second uses the array descriptor on the type. The third and fourth versions vary the position of the array descriptor as well as employ the verbose version of object construction and initialization using the New keyword to invoke the constructor.

IAsyncResult Interface and EndInvoke

The object implementing the IAsyncResult interface returned by BeginInvoke implements four public properties, providing you with a variety of ways to manage the asynchronous process. These properties are AsyncState, AsyncWaitHandle, CompletedSynchronously, and IsCompleted. Table 14.2 briefly describes the uses for these properties.

Table 14.2. IAsyncResult Public Properties
Property Name Description
AsyncState Gets user-defined information about an asynchronous process
AsyncWaitHandle A WaitHandle that can be used to wait for the asynchronous process
CompletedSynchronously Returns a Boolean indicating if the asynchronous process returned synchronously
IsCompleted A Boolean indicating that the process has completed

The EndInvoke method takes a specific IAsyncResult object returned by BeginInvoke and blocks until the asynchronous behavior returns. Upon return, the IAsyncResult object contains information about the state of the asynchronous process, and EndInvoke returns an object representing the return value of the invoked method.

If you add the following code to the example in Listing 14.8, the code will block until the ListBox is loaded and display the results of the asynchronous process.

 EndInvoke(AsynchResult) MsgBox(AsynchResult.IsCompleted) 

Insert the preceding two lines of code at line 24 of Listing 14.8. The EndInvoke method will block and the MsgBox statement will display a Boolean value indicating whether or not the process finished.


Team-Fly    
Top
 


Visual BasicR. NET Unleashed
Visual BasicR. NET Unleashed
ISBN: N/A
EAN: N/A
Year: 2001
Pages: 222

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net