In the June 11 edition of Developer .NET Perspectives, I wrote about creating a second thread within your process. This week, I expand on that topic by showing you how to communicate with that thread.
Logically, a main thread can communicate with a worker thread three times: at start, at finish, and while running. The first opportunity, at start, actually occurs before the thread starts. Remember that the ThreadStart class contains a method's address. Under Microsoft .NET, this method is attached to the instance of the worker class. When you define your worker class, you can assign a property (e.g., Public Message of type String) to that class.
To show you how simple working with threads in .NET can be, let's walk through the steps of using Visual Studio .NET to create a basic demonstration. We'll work with Visual Basic (VB) to create a simple MyThread application that will create a second thread and let you stop that thread after it has started.
Start by creating a simple VB Windows form project, and place a button on the resulting form. Add a new class called Worker to your project. In this empty class, add the public property Message and a method of 'Run'. The 'Run' method simply calls a MessageBox with the class's Message property. On the form, create the OnClick event handler for your button. In the OnClick method's code block, begin by creating a new instance of the project's Worker class. Also in the OnClick method, add a value (e.g., 'Hello World') to the Worker class's Message property. Next, add the code to create an instance of the ThreadStart class. The constructor relies on the result of applying the 'AddressOf' operator on the Worker.Run method that will run when the worker thread is started. After you add this code, you can create an instance of a Thread object that’'s based on this ThreadStart instance. Finally, add the line that actually calls the instance method Start on the Thread object. Now, test the project and ensure that pressing the button creates the thread and displays the message box with the message passed to the thread.
You've now created a form that can communicate to a worker thread to start. If the message was the name of a file to process, this form would pass that information into the worker thread. This ability to pass information into a thread provides a way to address the second communication opportunity: at finish. Add a simple label to the Windows form and leave the default name of Label1. Then, add a Public property called mylabel to the Worker class and define it as type System.Windows.Forms.Label. Add a line to the end of the 'Run' method that sets the mylabel Text property to "Done". The final step is to modify the OnClick event handler related to the Windows form. Before you start the thread, set the Worker thread's mylabel property to the Label1 object that you added to the form.
When you run the MyThread application, everything should work smoothly, but how do you know that events are really threaded? In the OnClick event handler, just after the instance of the thread is started, add a line to set the Text property of Label1 to Running. The MessageBox in the worker thread blocks that thread's final command until the MessageBox is cleared. Thus, when you test this version, the UI will display the label text "Running" until you close the message box and the worker thread can finish.
The third communication opportunity occurs while the thread is running. You can enable this type of communication by simply setting a property in the thread, but what you really need is a shared object between the two threads that permits status and information to be interchanged. The structure should support the equivalent of 2-way communication so that the main thread can ask the worker thread to stop and the worker thread can display its status relative to completion.
Add another class, Status, to the project, and this time use the keyword Shared to create a property. The declaration should look like
Public Shared StopWork as Integer
Then, add a new button to the Windows form, with a caption of Stop. In the button's OnClick event handler, simply reference the class by name and set this property to 1 (i.e., Status.StopWork = 1). Notice that you don't create an instance of the Status class. Shared properties don't require an instance because only one value is possible, regardless of the number of instances of a class. In fact, this example uses zero instances, but one copy of these values occurs. For more information about the Shared keyword, go to the following URL.
Before testing the MyThread application, go to the Worker class's Run method and add code for a simple For...Next loop around your existing MessageBox call. Within this loop, add a conditional If statement that says
If Shared.StopWork = 1 Then Exit For
The worker thread will keep reopening its MessageBox until the StopWork value is set to 1. Start the project, and you can test that the MessageBox repeatedly appears until the user clicks the Stop button on the main form and exits the loop.
The Shared keyword lets Visual Studio .NET developers create both shared properties and shared methods. Remember that because shared properties and especially shared methods aren't associated with an instance of a class, they can't access instance data for that class—only shared data. Finally, shared methods aren't completely thread-safe and only begin to address the power and risks of multithreaded applications.
Creating thread-safe code is a future threading topic; however, shared properties and shared methods support access to the same data across different threads. Adding a second shared property called PercentComplete that the worker thread could update with its current status and that the main thread could use to display that status to the user is a simple process. Now you understand not only how simple working with threads in .NET can be but also how easily you can transfer a simple design into working code. If you would like a copy of the code I wrote following this design, feel free to email me.