15.9. Multithreading with GUIsThe 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.
Figure 15.14. GUIThreads demonstrates multithreading in a GUI application.
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. |