Visual Basic 2005 for Programmers (2nd Edition)

15.9. Multithreading with GUIs

The nature of multithreaded programming prevents you from knowing exactly when a thread will execute. GUI controls are not thread safeif multiple threads manipulate a control, the results may not be correct. To ensure that threads manipulate controls in a thread-safe manner, all interactions with them should be performed by the User Interface thread (also known as the UI thread)the thread that creates and maintains the GUI. Class Control provides method Invoke to help with this process. Method Invoke specifies GUI processing statements that the UI thread should execute. The method receives as its arguments a Delegate representing a method that will modify the GUI and an optional array of objects representing the parameters to the method. At some point after Invoke is called, the UI thread will execute the method represented by the Delegate, passing to the contents of the object array as the method's arguments.

Our next example (Figs. 15.1315.14) uses separate threads to modify the content displayed in a Windows GUI. This example also demonstrates how to use thread synchronization to suspend a thread (i.e., temporarily prevent it from executing) and to resume a suspended thread. The GUI for the application contains three Labels and three CheckBoxes. Each thread in the program displays random characters in a particular Label. The user can temporarily suspend a thread by clicking the appropriate CheckBox and can resume the thread's execution by clicking the same CheckBox again.

Figure 15.13. Class RandomLetters outputs random letters and can be suspended.

1 ' Fig. 15.13: RandomLetters.vb 2 ' Writes a random letter to a label 3 Imports System.Threading 4 5 Public Class RandomLetters 6 Private Shared generator As New Random() ' for random letters 7 Private suspended As Boolean = False ' true if thread is suspended 8 Private output As Label ' Label for output 9 Private threadName As String ' name of the current thread 10 11 ' constructor 12 Public Sub New(ByVal label As Label) 13 output = label 14 End Sub ' New 15 16 ' delegate that allows method DisplayCharacter to be called 17 ' in the thread that creates and maintains the GUI 18 Delegate Sub DisplayDelegate(ByVal displayChar As Char) 19 20 ' method DisplayCharacter sets the Label's Text property 21 ' in a thread-safe manner 22 Private Sub DisplayCharacter(ByVal displayChar As Char) 23 ' output character in Label 24 output.Text = threadName + ": " + displayChar 25 End Sub ' DisplayCharacter 26 27 ' place random characters in GUI 28 Public Sub Run() 29 ' get name of executing thread 30 threadName = Thread.CurrentThread.Name 31 32 While True ' infinite loop; will be terminated from outside 33 ' sleep for up to 1 second 34 Thread.Sleep(generator.Next(1001)) 35 36 SyncLock Me ' obtain lock 37 While suspended ' loop until not suspended 38 Monitor.Wait(Me) ' suspend thread execution 39 End While 40 End SyncLock 41 42 ' select random uppercase letter 43 Dim displayChar As Char = ChrW(generator.Next(26) + 65) 44 45 ' display character on corresponding Label 46 output.Invoke(New DisplayDelegate(AddressOf DisplayCharacter), _ 47 New Object() {displayChar}) 48 End While 49 End Sub ' Run 50 51 ' change the suspended/running state 52 Public Sub Toggle() 53 suspended = Not suspended ' toggle bool controlling state 54 55 ' change label color on suspend/resume 56 If suspended Then 57 output.BackColor = Color.Red 58 Else 59 output.BackColor = Color.LightGreen 60 End If 61 62 SyncLock Me ' obtain lock 63 If Not suspended Then ' if thread resumed 64 Monitor.Pulse(Me) 65 End If 66 End SyncLock 67 End Sub ' Toggle 68 End Class ' RandomLetters

Figure 15.14. GUIThreads demonstrates multithreading in a GUI application.

1 ' Fig. 15.14: frmGUIThreads.vb 2 ' Demonstrates using threads in a GUI 3 Imports System.Threading 4 5 Public Class frmGUIThreads 6 Private letter1 As RandomLetters ' first randomLetters object 7 Private letter2 As RandomLetters ' second randomLetters object 8 Private letter3 As RandomLetters ' third randomLetters object 9 10 Private Sub frmGUIThreads_Load(ByVal sender As System.Object, _ 11 ByVal e As System.EventArgs) Handles MyBase.Load 12 13 ' create first thread 14 letter1 = New RandomLetters(lblThread1) 15 Dim firstThread As New Thread(New ThreadStart( _ 16 AddressOf letter1.Run)) 17 firstThread.Name = "Thread 1" 18 firstThread.Start() 19 20 ' create second thread 21 letter2 = New RandomLetters(lblTread2) 22 Dim secondThread As New Thread(New ThreadStart( _ 23 AddressOf letter2.Run)) 24 secondThread.Name = "Thread 2" 25 secondThread.Start() 26 27 ' create third thread 28 letter3 = New RandomLetters(lblThread3) 29 Dim thirdThread As New Thread(New ThreadStart( _ 30 AddressOf letter3.Run)) 31 thirdThread.Name = "Thread 3" 32 thirdThread.Start() 33 End Sub ' frmGUIThreads_Load 34 35 ' close all threads associated with this application 36 Private Sub frmGUIThreads_FormClosing(ByVal sender As System.Object, _ 37 ByVal e As System.Windows.Forms.FormClosingEventArgs) _ 38 Handles MyBase.FormClosing 39 System.Environment.Exit(System.Environment.ExitCode) 40 End Sub ' frmGUIThreads_FormClosing 41 42 Private Sub chkThread_CheckedChanged(ByVal sender As System.Object, _ 43 ByVal e As System.EventArgs) Handles chkThread1.CheckedChanged, _ 44 chkThread3.CheckedChanged, chkThread2.CheckedChanged 45 If sender.Equals(chkThread1) Then 46 letter1.Toggle() 47 ElseIf sender.Equals(chkThread2) Then 48 letter2.Toggle() 49 ElseIf sender.Equals(chkThread3) Then 50 letter3.Toggle() 51 End If 52 End Sub ' chkThread_CheckedChanged 53 End Class ' frmGUIThreads

Class RandomLetters (Fig. 15.13) contains method Run (lines 2849), which takes no arguments and does not return any values. Line 30 uses Shared Thread property current-Thread to determine the currently executing thread, then uses the thread's Name property to get the thread's name. Each executing thread is assigned a name that includes the number of the thread in the Main method (see the output of Fig. 15.14). Lines 3248 are an infinite loop, which is in a separate thread from the main thread. When the application window is closed in this example, all the threads created by the main thread are closed as well, including threads (such as this one) that are executing infinite loops. In each iteration of the loop, the thread sleeps for a random interval from 0 to 1 second (line 34).

When the thread awakens, line 36 locks this RandomLetters object. so we can determine whether the thread has been suspended (i.e., the user clicked the corresponding CheckBox). Lines 3739 loop while the Boolean variable suspended remains TRue. Line 38 calls Monitor method Wait on this RandomLetters object to temporarily release the lock and place this thread into the WaitSleepJoin state. When this thread is Pulsed (i.e., the user clicks the corresponding CheckBox again), it moves back to the Running state. If suspended is False, the thread resumes execution. If suspended is still TRue, the loop executes again and the thread re-enters the WaitSleepJoin state.

Line 43 generates a random uppercase character. Lines 4647 call method Invoke passing to it a New DisplayDelegate containing the method DisplayCharacter and a New array of Objects that contains the randomly generated letter. Line 18 declares a Delegate type named DisplayDelegate, which represents methods that take a Char argument and do not return a value. Method DisplayCharacter (lines 2225) meets those requirementsit receives a Char parameter named displayChar and does not return a value. The call to Invoke in lines 4647 will cause the UI thread to call DisplayCharacter with the randomly generated letter as the argument. At that time, line 24 will replace the text in the Label associated with this RandomLetters object with the name of the THRead executing this RandomLetters object's GenerateRandomCharacters method and the randomly generated letter.

When the user clicks the CheckBox to the right of a Label, the corresponding thread should be suspended (temporarily prevented from executing) or resumed (allowed to continue executing). Suspending and resuming a thread can be implemented by using thread synchronization and Monitor methods Wait and Pulse. Lines 5267 declare method Toggle, which will change the suspended/resumed state of the current thread. Line 53 reverses the value of Boolean variable suspended. Lines 5660 change the background color of the Label by assigning a color to Label property BackColor. If the thread is suspended, the background color will be Color.Red. If the thread is running, the background color will be Color.LightGreen. Method Toggle is called from the event handler in Fig. 15.14, so its tasks will be performed in the UI threadthus, there is no need to use Invoke for lines 57 and 59. Line 62 locks this RandomLetters object so we can determine whether the thread should resume execution. If so, line 64 calls method Pulse on this RandomLetters object to alert the thread that was placed in the WaitSleepJoin state by the Wait method call in line 38.

Note that the If statement in line 63 does not have an associated Else. If this condition fails, it means that the thread has just been suspended. When this happens, a thread executing in line 37 will enter the while loop and line 38 will suspend the thread with a call to method Wait.

Class frmGUIThreads (Fig. 15.14) displays three Labels and three CheckBoxes. A separate thread of execution is associated with each Label and CheckBox pair. Each thread randomly displays letters from the alphabet in its corresponding Label object. Lines 14, 21 and 28 create three new RandomLetters objects. Lines 1516, 2223 and 2930 create three new THReads that will execute the RandomLetters objects' GenerateRandomCharacters methods. Lines 17, 24 and 31 assign each Thread a name, and lines 18, 25 and 32 Start the Threads.

If the user clicks the Suspended CheckBox next to a Label, event handler chkThread_CheckedChanged (lines 4252) determines which CheckBox was clicked and calls its associated RandomLetters object's Toggle method to suspend or resume the thread.

Lines 3640 define the frmGUIThreads_FormClosing event handler, which calls method Exit of class System.Environment with the ExitCode property as an argument. This causes all other threads in this application to terminate. Otherwise, only the UI thread would be terminated when the user closes this application; Thread1, Thread2 and Thread3 would continue executing forever.

Категории