Abstract: the. NET Framework provides new classes to easily create multi-threaded applications. This article describes how to use the multi-Thread Programming Technology of Visual Basic. NET to develop applications with higher efficiency and faster response speed.
Directory
Introduction
Advantages of Multithreading
Create a thread
Synchronization thread
Thread Timer
Cancel task
Summary
Introduction
In the past, Applications created by Visual Basic developers were all synchronous applications executed by program tasks in sequence. Although multi-threaded applications have higher efficiency because multiple tasks run almost simultaneously, it is difficult to use earlier versions of Visual Basic to create such applications.
An operating system function called multitasking makes multithreading possible. It can simulate the function of Running multiple applications at the same time. Although most PCs only have one processor installed, the modern operating system provides multi-task processing by allocating the processor time to multi-segment executable code (called threads. A thread can represent the entire application, but usually only represents a part of the application that can run independently. The operating system allocates processing time for each thread Based on the thread priority and the time after the last running thread. Multithreading can significantly improve performance when executing time-consuming tasks (such as file input and output.
But pay attention to one problem. Although multithreading can improve the performance, each thread requires additional memory to create a thread and the processor time to run the thread. If too many threads are created, the performance of the application will be reduced. When designing multi-threaded applications, you should weigh the benefits and costs of adding more threads.
Multi-task processing has become part of the operating system for a long time. However, until recently, Visual Basic programmers have been able to execute multi-threaded tasks only through the informal release function, or indirectly implement this function by using COM components or asynchronous components of the operating system. The. NET Framework provides comprehensive support for developing multi-threaded applications in the System. Threading namespace.
This article discusses some advantages of multithreading and how to use Visual Basic. NET to develop Multithreaded Applications. Although Visual Basic. NET and. NET framework makes the development of multi-threaded applications very simple, but this article is mainly for middle and senior developers, and is transitioning from an earlier version of Visual Basic to Visual Basic. NET developers. For beginners of Visual Basic. NET, first read the Corresponding topics in Visual Basic Language Tour (English.
This article is not a comprehensive discussion of multi-threaded programming. For more information, see other resources listed at the end of this article.
Advantages of Multithreading
It is easier to develop synchronization applications. However, because the previous task must be completed before a new task can be started, the efficiency is usually lower than that of multi-threaded applications. If the synchronization task takes longer than expected, the application may not respond. Multi-threaded processing can run multiple processes at the same time. For example, a text processor application can check spelling (as a separate task) while processing documents ). Since multi-threaded applications divide programs into independent tasks, performance can be significantly improved in the following aspects:
Multi-thread technology makes the response speed of the program faster, because the user interface can continue to be active while performing other work.
Tasks that are not currently processed can give the processor time to other tasks.
Tasks that take up a large amount of processing time can regularly give the processor time to other tasks.
You can stop a task at any time.
You can set the priority of each task to optimize the performance.
Whether a multi-threaded application needs to be created depends on multiple factors. In the following cases, multithreading is the best choice:
Tasks that consume time or a large number of processors are congested on the user interface.
Each task must wait for external resources (such as remote files or Internet connections ).
For example, the Internet application "robot" is used to track links on Web pages and download files that meet specific conditions ". Such applications can synchronously download each object in sequence, or use multiple threads to download multiple objects at the same time. The multi-threaded method is much more efficient than the synchronization method, because the remote Web server can download files even if the response is very slow in some threads.
Create a thread
The most direct way to create a thread is to create a new Thread class instance and use the AddressOf statement to pass the delegate for the process to be run. For example, the following code runs a sub-process named SomeTask as a separate thread.
Dim Thread1 As New System. Threading. Thread (AddressOf SomeTask)
Thread1.Start
'The code here is run immediately.
The above describes how to create and start a thread. Any code after the thread Start method is called will run immediately without waiting for the previous thread to finish running.
The following table lists some methods used to control each thread. Method operation
Start starts the thread.
Sleep suspends a thread for a specified period of time.
The Suspend will Suspend the thread after it reaches the security point.
Abort stops the thread after it reaches the security point.
Resume restarts the suspended thread.
Join causes the current thread to wait for other threads to finish running. If a timeout value is used and the thread ends within the allocated time, this method returns True.
Most methods do not need to be described, but "security points" may be a new concept. Security points refer to some locations in the Code. When these locations are run in the public language, automatic garbage collection can be safely executed, that is, unused variables can be released and memory can be recycled. When the Abort or Suspend method of a thread is called, the code is analyzed and the proper position of the thread to stop running is determined when the common language runs.
The thread also contains many useful attributes, as shown in the following table: attribute values
Isalive contains the value true if the thread is active.
Isbackground gets or sets a Boolean value to indicate whether the thread is a background thread or whether it should be a background thread. The background thread is similar to the foreground thread, but the background thread does not stop the process from terminating. When all foreground threads of a process are terminated, the common language runtime will call the abort method for the background threads that are still in the active state to end the process.
Name gets or sets the thread name. It is often used to find various threads during debugging.
Priority gets or sets the value of the operating system used to determine the thread priority.
Apartmentstate gets or sets the thread model used for a specific thread. When a thread calls an unmanaged code, the thread model is very important.
Threadstate contains the value indicating the thread status.
Thread attributes and methods are very useful for creating and managing threads. The thread synchronization section in this article describes how to use these attributes and methods to control and coordinate threads.
Thread parameters and return values
The method call in the preceding example cannot contain any parameters or return values. This restriction is one of the main drawbacks of using this method to create and run threads. However, you can encapsulate the processes running in a separate thread into a class or structure, provide them with parameters, and enable them to return parameters.
Class tasksclass
Friend strarg as string
Friend retval as Boolean
Sub sometask ()
'Use the strarg field as a parameter.
Msgbox ("strarg contains strings" & strarg)
Retval = true' sets the return value of the returned parameter.
End sub
End Class
'To use a class, set the attributes or fields of the stored parameters,
Then, call the method asynchronously as needed.
Sub dowork ()
Dim tasks as new tasksclass ()
Dim thread1 as new system. Threading. Thread (_
Addressof tasks. sometask)
Tasks. strarg = "a parameter" 'sets the fields used as parameters.
Thread1.start () 'starts a new thread.
Thread1.join () 'waits until thread 1 finishes running.
'Display the return value.
Msgbox ("Return Value of thread 1" & tasks. retval)
End sub
Manual creation and management of threads are most suitable for applications that require detailed control (such as thread priority and thread model. As you can imagine, it is very difficult to manage a large number of threads using this method. If multithreading is required, consider using a thread pool to reduce complexity.
Thread Pool
A thread pool is a form of multithreading. In the thread pool, when a thread is created, the task is added to the queue and started automatically. With the thread pool, you can use the delegate of the process to be run to call the threadpool. queueuserworkitem method. Visual Basic. NET will create a thread and run the process. The following example shows how to use the thread pool to start multiple tasks.
Sub dowork ()
Dim tpool as system. Threading. threadpool
'Queue a task
Tpool. queueuserworkitem (new system. Threading. waitcallback _
(Addressof somelongtask ))
'Queue another task
Tpool. queueuserworkitem (new system. Threading. waitcallback _
(Addressof anotherlongtask ))
End sub
If you want to start many independent tasks, but you do not need to set the attributes of each thread separately, the thread pool will be very useful. Each thread is started with the default stack size and priority. By default, each system processor can run a maximum of 25 thread pool threads. Other threads that exceed this limit will be queued until the other threads finish running.
One advantage of the thread pool is that the parameters in the State object can be passed to the task process. If the calling process requires multiple parameters, You can forcibly convert the class structure or instance to the object data type.
Parameters and return values
The Return Value of the thread from the thread pool is a bit complicated. Standard methods that return values from function calls are not allowed, because only sub processes can queue into the thread pool. One way to provide parameters and return values is to encapsulate parameters, return values, and methods into the packaging class, as described in thread parameters and return values. A simpler way to provide parameters and return values is to use the byval State object variable of the queueuserworkitem method (optional ). If this variable is used to pass the reference to the instance of the class, the members of the instance can be modified by the thread pool thread and used as the return value. You can modify the object referenced by the variable (passed by value). This may not be obvious at the beginning, but it is indeed possible because only the object reference is passed by value. After modifying the object member referenced by the object reference, these changes will be applied to the actual class instance.
The value in the status object cannot be returned using the structure. Because the structure is a value type, changes made by the asynchronous process do not change the members of the original structure. If no return value is required, you can use the structure to provide parameters.
Friend Class StateObj
Friend StrArg As String
Friend IntArg As Integer
Friend RetVal As String
End Class
Sub ThreadPoolTest ()
Dim TPool As System. Threading. ThreadPool
Dim StObj1 As New StateObj ()
Dim StObj2 As New StateObj ()
'Set some fields as parameters in the status object.
StObj1.IntArg = 10
StObj1.StrArg = "a string"
StObj2.IntArg = 100
StObj2.StrArg = "another string"
'Queue a task
TPool. QueueUserWorkItem (New System. Threading. WaitCallback _
(AddressOf SomeOtherTask), StObj1)
'Queue another task
TPool. QueueUserWorkItem (New System. Threading. WaitCallback _
(AddressOf AnotherTask), StObj2)
End Sub
Sub SomeOtherTask (ByVal StateObj As Object)
'Use the status object field as a parameter.
Dim StObj As StateObj
StObj = CType (StateObj, StateObj) 'is forcibly converted to the correct type.
MsgBox ("StrArg contains strings" & StObj. StrArg)
MsgBox ("IntArg contains numbers" & CStr (StObj. IntArg ))
'Use the field as the return value.
StObj. RetVal = "Return Value of SomeOtherTask"
End Sub
Sub AnotherTask (ByVal StateObj As Object)
'Use the status object field as a parameter.
'Status Object is passed as an Object.
'Forcibly convert it to a specific type to make it easier to use.
Dim StObj As StateObj
StObj = CType (StateObj, StateObj)
MsgBox ("StrArg contains strings" & StObj. StrArg)
MsgBox ("IntArg contains numbers" & CStr (StObj. IntArg ))
'Use the field as the return value.
Stobj. retval = "Return Value of anothertask"
End sub
When the common language is running, it automatically creates threads for queued thread pool tasks, and then releases these resources after the tasks are completed. It is difficult to cancel a task after it is queued. Threadpool threads always run using the multi-thread unit (MTA) thread model. If you need to use a single-threaded unit (STA) model thread, You Should manually create a thread.
Synchronization thread
Synchronization provides a compromise between the unstructured nature of multi-thread programming and the structured order of synchronization processing.
With synchronization technology, you can do the following:
When a task must be executed in a specific order, it explicitly controls the order in which the code runs.
-Or-
When two threads share the same resource at the same time, avoid possible problems.
For example, you can use synchronization to make the display process wait until the data retrieval process running in another thread ends.
There are two methods for synchronization: polling and using synchronization objects. Round Robin checks the status of asynchronous calls repeatedly from the loop. The efficiency of using polling management threads is the lowest, because repeated checks on the status of various thread attributes will waste a lot of resources.
For example, you can use the isalive attribute if you want to check whether the thread has ended during polling. Be careful when using this attribute because the active thread is not necessarily running. You can use the threadstate attribute of a thread to obtain detailed information about the thread state. Because threads may be in multiple States at any given time, the value stored in threadstate can be a combination of values in the system. Threading. threadstate enumeration. Therefore, the status of all related threads should be carefully checked during polling. For example, if the thread State indicates that it is not running, the thread may have completed. On the other hand, it may also be suspended or in sleep state.
As you can imagine, polling sacrifices some of the advantages of multithreading to control the running thread order. Therefore, you can use a highly efficient join method to control threads. The Join Operation puts the call process in a waiting state until the thread completes or the call times out (if a timeout is specified ). The name "join" comes from this idea, that is, the new thread created is a branch of the execution path. Using join, you can merge separate execution paths into one thread again.
Figure 1: Thread
Note that join is a synchronous call or a blocking call. After the join or wait handle wait method is called, the call process stops and waits for the thread to send a signal to notify it that it has completed.
Sub jointhreads ()
Dim thread1 as new system. Threading. Thread (addressof sometask)
Thread1.start ()
Thread1.join () 'waits for the thread to finish running.
Msgbox ("End of thread running ")
End sub
These simple methods of controlling threads are useful for managing a small number of threads, but not suitable for large projects. The next section discusses some advanced technologies that can be used to synchronize threads.
Advanced Synchronization Technology
Multi-threaded applications usually use the wait handle and monitor object to synchronize multiple threads. The following table describes some. NET Framework classes that can be used to synchronize threads. Usage
Autoresetevent wait handle, used to notify one or more wait threads of an event. Autoresetevent automatically changes the status to sent signal after waiting for the thread to be released.
Interlocked provides atomic operations for variables shared by multiple threads.
ManualResetEvent wait handle, used to notify one or more wait threads of an event. The status of the manually Reset event remains as sent until the Reset method sets it as not sent. Similarly, the status remains unchanged until the Set method sets it to the status of the sent signal. When the object status is sent, any number of waiting threads (that is, threads that call a waiting function to start waiting for the specified event object) can be released.
Monitor provides a mechanism for Synchronous object access. The Visual Basic. NET application calls SyncLock to use the monitor object.
Mutex wait handle, which can be used for inter-process synchronization.
The ReaderWriterLock definition is used to lock a single writer and multiple readers.
Timer provides a mechanism to run tasks at specified intervals.
WaitHandle encapsulates the objects that are unique to the operating system and are waiting for exclusive access to the shared resources.
Waiting handle
The waiting handle is the object that notifies another thread of the State of one thread. A thread can use a wait handle to notify other threads that they need to exclusively access resources. Then, other threads can use this resource only when no thread uses the wait handle. The waiting handle has two statuses: Sent signal and not sent signal. The wait handle that does not belong to any thread is in the sent signal state. The waiting handle of a thread is in the state of not sending a signal.
A thread requests the ownership of the waiting handle by calling a wait method (such as WaitOne, WaitAny, or WaitAll. The wait method is similar to the Join method of a separate thread to block calls.
If no other thread has the waiting handle, the call will return True immediately. The waiting handle status will change to unsent, And the thread with the waiting handle will continue to run.
If the thread calls a waiting method for the waiting handle, but the waiting handle belongs to the other thread, the calling thread will wait for the specified time (if a timeout is specified ), or wait indefinitely (no timeout is specified) until other threads release the wait handle. If timeout is specified and the waiting handle is released before the timeout expires, True is returned for the call. Otherwise, the call returns False and the calling thread continues to run.
The Set method is called when a thread with a pending handle ends or does not need to wait for the handle. After other threads call the Reset method, call WaitOne, WaitAll, or WaitAny, and successfully wait for a thread to call the Set method, they can Reset the waiting handle status to unsent signals. After a single waiting thread is released, the system automatically resets the AutoResetEvent handle to unsent signals. If no thread is in the waiting state, the event object State will remain as a signal. Usage
WaitOne accepts a waiting handle as a parameter and puts the calling thread in the waiting state until another process calls Set to Set the current waiting handle to a sent signal.
WaitAny accepts an array of waiting handles as a parameter and puts the calling thread in the waiting state until any specified waiting handle has been Set to sent by calling Set.
WaitAll accepts an array of waiting handles as a parameter and puts the calling thread in the waiting state until all the specified waiting handles have been Set to sent by calling Set.
Set sets the status of the specified waiting handle to sent signals, and enables any waiting thread to continue running.
Reset sets the status of the specified event to not sending a signal.
Visual Basic. NET has three common wait handles: mutex object, ManualResetEvent, and AutoResetEvent. The last two types are usually called synchronization events.
Mutex object
A mutex is a synchronization object that can only be owned by one thread at a time. In fact, the name "mutex" comes from the fact that the ownership of the mutex object is mutually exclusive. If the thread wants to exclusively access the resource, it needs to request the ownership of the mutex object. Since only one thread can have a mutex object at any time, other threads must wait until the ownership of the mutex object is obtained before using resources.
The WaitOne method allows the calling thread to wait for the ownership of the mutex object. If the thread that owns the mutex object terminates normally, the state of the mutex object is set to sent, and the next waiting thread obtains ownership.
Synchronization event
A synchronization event is used to notify other threads that something has occurred or a resource is available. Do not be misled by these items that use the word "Event. Unlike other Visual Basic events, synchronization events are actually waiting handles. Similar to other wait handles, a synchronization event has two statuses: Sent signal and not sent signal. The thread that calls a synchronous event wait method must wait until another thread sends a notification to the event by calling the Set method. There are two types of synchronization events. The thread uses the Set method to Set the ManualResetEvent instance status to sent signals. The thread uses the Reset method or sets the status of the ManualResetEvent instance to no signal when controlling the response to a call waiting for WaitOne. You can also use Set to Set AutoResetEvent instances to sent signals. However, as long as the thread is notified that the event has sent a signal, these instances will automatically return to the not sent Signal status.
The following example uses the AutoResetEvent class to synchronize thread pool tasks.
Sub StartTest ()
Dim AT As New AsyncTest ()
AT. StartTask ()
End Sub
Class AsyncTest
Private Shared AsyncOpDone As New _
System. Threading. AutoResetEvent (False)
Sub StartTask ()
Dim Tpool As System. Threading. ThreadPool
Dim arg As String = "SomeArg"
Tpool. QueueUserWorkItem (New System. Threading. WaitCallback (_
AddressOf Task), arg) 'queues a Task.
AsyncOpDone. WaitOne () 'waits for the thread to call Set.
MsgBox ("the thread stops running. ")
End Sub
Sub Task (ByVal Arg As Object)
MsgBox ("the thread is starting. ")
System. Threading. Thread. Sleep (4000) ': Wait for 4 seconds.
MsgBox ("State object contains string" & CStr (Arg ))
AsyncOpDone. Set () 'indicates the end of the thread.
End Sub
End Class
Monitor object and SyncLock
The monitor object is used to ensure that the code block is not interrupted by other threads during runtime. In other words, the code in other threads cannot run until the code in the synchronous code block is completed. In Visual Basic. NET, the SyncLock keyword is used to simplify access to the monitor object. Use the Lock keyword in Visual C #. NET.
For example, suppose there is a program that reads data repeatedly and asynchronously and displays the result. If the operating system uses the preemptible multitasking technology, it can interrupt the running thread and use the time to run another thread. If data is not synchronized, if the data object is modified by another thread when the data is displayed, some updated data may be seen. The SyncLock statement ensures that the code segment is not interrupted during running. The following example shows how to use SyncLock to provide exclusive access to data objects during the display process.
Class DataObject
Public ObjText As String
Public ObjTimeStamp As Date
End Class
Sub RunTasks ()
Dim MyDataObject As New DataObject ()
ReadDataAsync (MyDataObject)
SyncLock MyDataObject
DisplayResults (MyDataObject)
End SyncLock
End Sub
Sub ReadDataAsync (ByRef MyDataObject As DataObject)
'Add code to asynchronously read and process data.
End Sub
Sub DisplayResults (ByVal MyDataObject As DataObject)
'Add code to display the result.
End Sub
To ensure that the code segment is not interrupted by code running in other threads, use SyncLock.
Interlocked class
To avoid problems that may occur when multiple threads attempt to update or compare the same value at the same time, you can use the Interlocked class method. This method enables you to securely increment, decrease, exchange, and compare values in any thread. The following example shows how to use the Increment method to Increment the variables shared by the processes running in other threads.
Sub ThreadA (ByRef IntA As Integer)
System. Threading. Interlocked. Increment (IntA)
End Sub
Sub ThreadB (ByRef IntA As Integer)
System. Threading. Interlocked. Increment (IntA)
End Sub
ReaderWriter locked
In some cases, you may want to lock the resource only when writing data, and allow multiple clients to read data simultaneously without updating data. The ReaderWriterLock class forces exclusive access to resources when the thread modifies resources, but allows non-exclusive access to resources when reading resources. ReaderWriter locking is a useful alternative to exclusive locking, because exclusive locking keeps other threads waiting, even if those threads do not need to update data. The following example shows how to use ReaderWriter to coordinate read/write operations on multiple threads.
Class ReadWrite
'Can be safely called from multiple threads
'Readdata and writedata methods.
Public readwritelock as new system. Threading. readerwriterlock ()
Sub readdata ()
'The process reads information from a source.
'The read lock prohibits writing data before the thread completes reading,
'Other threads are allowed to call readdata at the same time.
Readwritelock. acquirereaderlock (system. Threading. Timeout. Infinite)
Try
'Here, the read operation is performed.
Finally
Readwritelock. releasereaderlock () 'releases the read lock.
End try
End sub
Sub writedata ()
'This process writes information to a source.
'The write lock is prohibited before the thread completes the write operation
'Read or write data.
Readwritelock. acquirewriterlock (system. Threading. Timeout. Infinite)
Try
'Write here.
Finally
ReadWriteLock. ReleaseWriterLock () 'releases the write lock.
End Try
End Sub
End Class
Deadlock
Thread Synchronization is very important in multi-threaded applications, but there is always a risk of deadlock when multiple threads wait for each other. Just like a car stops in four directions, everyone is waiting for another person to go, and the deadlock stops all operations. Obviously, it is very important to avoid deadlocks. There are many situations that can lead to deadlocks. Likewise, there are many ways to avoid deadlocks. Although this article does not have enough space to discuss all issues related to deadlocks, it is very important that careful planning is the key to avoiding deadlocks. A multi-threaded application can be illustrated before coding. Generally, deadlocks can be predicted.
Thread Timer
The Threading. Timer class is very useful for running tasks on a regular basis in a separate thread. For example, you can use a thread timer to check the database status and integrity, or back up important files. The following example starts a task every two seconds and uses a flag to start the Dispose method that stops the timer. In this example, the status is sent to the output window. Therefore, before testing the code, press CONTROL + ALT + O to make the window visible.
Class StateObjClass
'Is used to retain the parameters required to call TimerTask
Public SomeValue As Integer
Public TimerReference As System. Threading. Timer
Public TimerCanceled As Boolean
End Class
Sub RunTimer ()
Dim StateObj As New StateObjClass ()
Stateobj. timercanceled = false
Stateobj. somevalue = 1
Dim timerdelegate as new threading. timercallback (addressof timertask)
'Create a timer for every 2 seconds.
'Note: There is no start method here. After the instance is created,
'Timer starts to run.
Dim timeritem as new system. Threading. Timer (timerdelegate, stateobj ,_
2000,200 0)
Stateobj. timerreference = timeritem 'saves a reference for dispose.
While stateobj. somevalue <10' runs 10 cycles.
System. Threading. thread. Sleep (1000) ': Wait 1 second.
End while
Stateobj. timercanceled = true 'request the dispose of the timer object.
End sub
Sub timertask (byval stateobj as object)
Dim state as stateobjclass = ctype (stateobj, stateobjclass)
Dim X as integer
'Use the Interlocked class to increment the counter variable.
System. Threading. Interlocked. Increment (State. SomeValue)
Debug. WriteLine ("A New thread has been started" & Now)
If State. TimerCanceled then' has requested Dispose.
State. TimerReference. Dispose ()
Debug. WriteLine ("completion time" & Now)
End If
End Sub