Virtual Developer Workshop: Containerized Development with Docker
In VB .Net you do not have to pull out the big multithread guns to return your application to responsiveness. You can get asynchronous, multithreaded behavior quickly and easily in VB .Net.
Visual Basic, under .Net, allows you to use a thread that is available in the ThreadPool. Using the thread pool you can off-load long tasks without writing a lot of extra code thread management code.
Benefits of Using ThreadPool
The ThreadPool is a dynamic group of threads that are available for you to use for multithreaded processing. The number of threads in the pool is dynamic and you are not required to do anything extra to manage the threads in the pool. If you request a thread and none are available, the pool may create a new thread or wait until an existing thread is available. What the pool actually does happens behind the scene and you can write your code worry-free.
To demonstrate using the ThreadPool and getting the same benefit as we would by constructing a new thread, we will contrive a problem. The problem simulates a Windows application with a time-intensive startup process. To coerce a simulation the Windows application will load ten million integers into a ListBox. If the application simply attempts to load the integers into the ListBox in the Load event handler then the main form may take several minutes to load. However, if we off-load the ListBox loading process to a separate thread then our main form loads up right away. (Keep in mind that extra benefit is derived only if we do not need the results of the long process to be available when the form is available.)
Using the contrived scenario I ran out of patience before the ListBox was loaded. Applying the multithreaded approach, the main form loaded immediately and the ListBox kept loading in the background. The visible part of the ListBox looked full while the list continued filling. Instead of a sluggish startup the application showed up immediately and was available for user-inputs, including scrolling through the list while it was filling. Let's take a look at how this is accomplished with using the ThreadPool class.
Work is added to the ThreadPool by adding a WaitCallBack delegate into the pool's queue. When the queue is ready it will use the delegate to complete the task. Although you do not need to manage the thread, if the thread delegate interacts with Windows Forms then you will need to be extra careful.
The WaitCallBack delegate is a delegate procedure that takes an Object argument. The following example demonstrates the signature of a WaitCallBack delegate.
Public Delegate Sub WaitCallBack(ByVal State As Object)
The address of a sub routine that has a single Object argument can be passed as the argument to the ThreadPool.QueueUserWorkItem method, adding the process defined in the procedure as a work item to be processed on a separate thread. (ThreadPool.QueueUserWorkItem is a shared method. You do not need to create an instance of ThreadPool to add a work item to the pool.) Listing 1 demonstrates code that off-loads the initialization of the list box to a separate thread.
Listing 1: Multithreading with an existing thread in ThreadPool.
1: Private Sub Form1_Load(ByVal sender As System.Object, _ 2: ByVal e As System.EventArgs) Handles MyBase.Load 3: 4: ListBox1.Items.Clear() 5: ThreadPool.QueueUserWorkItem(AddressOf Initialize) 6: 7: End Sub 8: 9: Private Elem As String 10: Private Sub Add() 11: ListBox1.Items.Add(Elem) 12: Application.DoEvents() 13: End Sub 14: 15: Private Sub Initialize(ByVal State As Object) 16: Dim I As Integer 17: 18: SyncLock ListBox1.GetType 19: 20: For I = 10000000 To 1 Step -1 21: Elem = I 22: ListBox1.Invoke(CType(AddressOf Add, MethodInvoker)) 23: Next 24: 25: End SyncLock 26: 27: End Sub
If we were to load the list when the form loaded then the form would not finish loading and be available until the long list loading process completed. Line 5 demonstrates how easy the ThreadPool is to use. By moving the list initialization into a separate procedure, matching the signature of the WaitCallBack delegate, we can construct a delegate and pass it to the ThreadPool queue. The WaitCallBack procedure in the delegate is defined on lines 15 to 27.
Figure 1: The Populate method running on thread 1300 as shown in the Threads window above.
AddressOf Initialize returns a delegate. The returned delegate is passed to the ThreadPool.QueueUserWorkItem shared method which immediately returns immediately returns. The ThreadPool fills the list using the Initialize method on a separate thread, using one of the threads in the pool (see figure 1). Because the Initialize method is on a separate thread and we interact with a Windows forms control—the ListBox1 control—we still need to call Invoke, as demonstrated on line 22, to safely update the ListBox control. Invoke synchronizes the interaction with the ListBox by placing a message in the Form's message queue, which is on the same thread as the ListBox. Because the Initialize method is on a separate thread we used a delegate and Invoke to add each element to the ListBox.
Tip: If a control's Handle is on a different thread than the code interacting with the control then you need to call the Control.Invoke method (see listing 1). If you are not sure then you can call Control.InvokeRequired which will return True if you need to use the Invoke method.
A final word regarding the state parameter: the State parameter is null in the example shown. You can pass an AutoResetEvent object as the second argument to an overloaded version of the ThreadPool.QueueUserWorkItem to manage synchronization of threads in the pool.
Employing the ThreadPool is an easy way to begin incorporating threads into your Visual Basic .Net applications. The ThreadPool manages constructing and releasing threads, and keeping track of the thread objects. Windows Forms controls are not thread-safe; consequently, you will need to use the Invoke method to interact with controls across thread boundaries.
You get the same performance from the ThreadPool as you would if you constructed a new Thread object. Consider using the ThreadPool whenever you need heavy-weight background processing. Of course, you still have the Timer and Application.Idle event for lightweight asynchronous processing, and you can always create instances of threads if you want to.
About the Author
Paul Kimmel is a freelance writer for Developer.com and CodeGuru.com. He is the founder of Software Conceptions, Inc, founded in 1990. Paul Kimmel performs contract software development services in North America and can be contacted at firstname.lastname@example.org.