Multithreaded Programming with Visual Basic .NET
Visual Studio Team
Summary: The .NET Framework provides new classes that make it easy to create multithreaded applications. This article discusses how you can use multithreaded programming techniques with Visual Basic® .NET to develop more efficient and responsive applications. (18 printed pages)
Traditionally, Visual Basic developers have created synchronous applications in which program tasks execute sequentially. Although multithreaded applications can be more efficient because multiple tasks run more or less simultaneously, they were difficult to create using previous versions of Visual Basic.
Multithreaded programs are possible because of an operating system feature called multitasking that simulates the ability to run multiple applications at the same time. Although most personal computers only have a single processor, modern operating systems provide multitasking by dividing processor time between multiple pieces of executable code called threads. A thread can represent an entire application, but more often it represents just one part of an application that can run separately. The operating system allocates processing time for each thread based on factors such as the thread's priority and how much time has passed since the thread last ran. Multiple threads can greatly improve performance when executing time-intensive tasks such as file input and output.
There is one cautionary note. Although multiple threads can improve performance, each thread has an associated cost in terms of additional memory required to create the thread and processor time required to keep it running. Creating too many threads can actually reduce your application's performance. When designing multithreaded applications, you should weigh the benefits of adding additional threads against their cost.
Multitasking has been a part of operating systems for a while. Until recently, however, Visual Basic programmers could only perform multithreaded tasks by using undocumented features or indirectly by using COM components or asynchronous parts of the operating system. The .NET Framework provides comprehensive support in the System.Threading namespace for developing multithreaded applications.
This article discusses some of the advantages of multithreading and how you can use Visual Basic .NET to develop multithreaded applications. Although Visual Basic .NET and the .NET Framework make it easy to develop multithreaded applications, this paper is intended for moderate to advanced developers as well as developers who are transitioning from earlier versions of Visual Basic to Visual Basic .NET. If you are just getting started using Visual Basic .NET, you should first read the topics in Visual Basic Language Tour.
While it is beyond the scope of this article to comprehensively discuss multithreaded programming, you may find more information in the additional resources at the end of this article.
Although synchronous applications are easier to develop, they are often less efficient than multithreaded applications because a new task cannot start until a previous task has finished. If completion of a synchronous task takes longer than expected, your application may appear unresponsive. Multithreaded processing can simultaneously run multiple procedures. For example, a word processor application can check spelling as a separate task while you continue to work on the document. Because multithreaded applications divide programs into independent tasks, they can substantially improve performance in the following ways:
- Multithreaded techniques can make your program more responsive because the user interface can remain active while other work continues.
- Tasks that are not currently busy can yield processor time to other tasks.
- Tasks that use a lot of processing time can periodically yield to other tasks.
- Tasks can be stopped at any time.
- You can set the priority of individual tasks higher or lower to optimize performance.
The decision to explicitly create a multithreaded application depends on several factors. Multithreading is best suited to situations where:
- Time-consuming or processor-intensive tasks block the user interface.
- Individual tasks must wait for an external resource, such as a remote file or Internet connection.
For example, consider an Internet "robot", an application that follows links on Web pages and downloads files that meet specific criteria. Such an application can either synchronously download each file one after another or use multithreading to download multiple files at the same time. The multithreaded approach can be much more efficient than the synchronous approach because files can be downloaded even when some threads receive slow responses from remote Web servers.
The most straightforward way to create a thread is by creating a new instance of the thread class and using the AddressOf statement to pass a delegate for the procedure you want to run. For example, the following code runs a sub procedure named
SomeTask as a separate thread.
Dim Thread1 As New System.Threading.Thread(AddressOf SomeTask) Thread1.Start ' Code here runs immediatly.
That is all there is to creating and starting a thread. Any code that follows a call to the thread's Start method runs immediately without waiting for the previous thread to finish.
The following table shows some of the methods you can use to control individual threads.
|Start||Causes a thread to start running.|
|Sleep||Pauses a thread for a specified time.|
|Suspend||Pauses a thread when it reaches a safe point.|
|Abort||Stops a thread when it reaches a safe point.|
|Resume||Restarts a suspended thread|
|Join||Causes the current thread to wait for another thread to finish. If used with a timeout value, this method returns True if the thread finishes in the allotted time.|
Most of these methods are self explanatory, but the concept of safe points may be new to you. Safe points are places in code where it is safe for the common language runtime to perform automatic garbage collection, the process of releasing unused variables and reclaiming memory. When you call the Abort or Suspend methods of a thread, the common language runtime analyzes the code and determines the location of an appropriate place for the thread to stop running.
Threads also contain a number of useful properties, as shown in the following table:
|IsAlive||Contains the value True if a thread is active.|
|IsBackground||Gets or sets a Boolean that indicates if a thread is or should be a background thread. Background threads are like foreground threads, but background threads do not prevent a process from terminating. Once all foreground threads belonging to a process have terminated, the common language runtime ends the process by calling the Abort method on background threads that are still alive.|
|Name||Gets or sets the name of a thread. Most commonly used to discover individual threads when debugging.|
|Priority||Gets or sets a value used by the operating system to prioritize thread scheduling.|
|ApartmentState||Gets or sets the threading model used for a particular thread. Threading models are important when a thread calls unmanaged code.|
|ThreadState||Contains a value that describes a thread's state or states.|
Thread properties and methods are useful when creating and managing threads. The thread synchronization section of this article discusses how you can use these properties and methods to control and coordinate threads.
The method call in the previous example cannot contain any parameters or return values. This limitation is one of the main drawbacks to creating and running threads in this way. However, you can provide and return arguments for procedures running on separate threads by wrapping them in either a class or a structure.
Class TasksClass Friend StrArg As String Friend RetVal As Boolean Sub SomeTask() ' Use the StrArg field as an argument. MsgBox("The StrArg contains the string " & StrArg) RetVal = True ' Set a return value in the return argument. End Sub End Class ' To use the class, set the properties or fields that store parameters, ' and then asynchronously call the methods as needed. Sub DoWork() Dim Tasks As New TasksClass() Dim Thread1 As New System.Threading.Thread( _ AddressOf Tasks.SomeTask) Tasks.StrArg = "Some Arg" ' Set a field that is used as an argument Thread1.Start() ' Start the new thread. Thread1.Join() ' Wait for thread 1 to finish. ' Display the return value. MsgBox("Thread 1 returned the value " & Tasks.RetVal) End Sub
Manually creating and managing threads is best suited to applications in which you want fine control of details such as thread priority and threading model. As you can imagine, it can be difficult to manage large numbers of threads in this way. Consider using thread pooling to reduce complexity when you need many threads.
Thread pooling is a form of multithreading where tasks are added to a queue and automatically started as threads are created. With thread pooling, you call the Threadpool.QueueUserWorkItem method with a delegate for the procedure you want to run, and Visual Basic .NET creates the thread and runs your procedure. The following example shows how you could use thread pooling to start several 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
Thread pooling is useful when you want to start many separate tasks without individually setting the properties of each thread. Each thread starts with a default stack size and priority. By default, up to 25 thread pool threads can run per system processor. Additional threads in excess of the limit can be queued, but they do not start until other threads finish.
One advantage of thread pooling is that you can pass arguments in a state object to the task procedure. If the procedure you are calling requires more than one argument, you can cast a structure or an instance of a class into an Object data type.
Parameters and Return Values
Returning values from a thread pool thread is a little tricky. The standard way of returning values from a function call is not allowed because Sub procedures are the only type of procedure that can be queued to a thread pool. One way you can provide parameters and return values is by wrapping the parameters, return values, and methods in a wrapper class as described in Thread Arguments and Return Values. An easer way to provide parameters and return values is by using the optional ByVal state object variable of the QueueUserWorkItem method. If you use this variable to pass a reference to an instance of a class, the members of the instance can be modified by the thread pool thread and used as return values. At first it may not be obvious that you can modify an object referred to by a variable that is passed by value. This is possible because only the object reference is passed by value. When you make changes to members of the object referred to by the object reference, the changes apply to the actual class instance.
Structures cannot be used to return values inside state objects. Because structures are value types, changes that the asynchronous process makes do not change the members of the original structure. Use structures to provide parameters when return values are not needed.
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 that act like parameters in the state object. StObj1.IntArg = 10 StObj1.StrArg = "Some string" StObj2.IntArg = 100 StObj2.StrArg = "Some other 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 state object fields as arguments. Dim StObj As StateObj StObj = CType(StateObj, StateObj) ' Cast to correct type. MsgBox("StrArg contains the string " & StObj.StrArg) MsgBox("IntArg contains the number " & CStr(StObj.IntArg)) ' Use a field as a return value. StObj.RetVal = "Return Value from SomeOtherTask" End Sub Sub AnotherTask(ByVal StateObj As Object) ' Use the state object fields as arguments. ' The state object is passed as an Object. ' Casting it to its specific type makes it easier to use. Dim StObj As StateObj StObj = CType(StateObj, StateObj) MsgBox("StrArg contains the String " & StObj.StrArg) MsgBox("IntArg contains the number " & CStr(StObj.IntArg)) ' Use a field as a return value. StObj.RetVal = "Return Value from AnotherTask" End Sub
The common language runtime automatically creates threads for queued thread pool tasks and then releases these resources when the tasks are done. There is no easy way to cancel a task once it has been queued. ThreadPool threads always run using the multithreaded apartment (MTA) threading model. You should manually create threads if you want threads that use the single threaded apartment model (STA).
Synchronization provides a compromise between the unstructured nature of multithreaded programming and the structured order of synchronous processing.
You use synchronization techniques:
- To explicitly control the order in which code runs whenever tasks must be performed in a specific sequence
- To prevent the problems that can occur when two threads share the same resource at the same time.
For example, you could use synchronization to cause a display procedure to wait until a data retrieval procedure that is running on another thread is complete.
There are two approaches to synchronization, polling and using synchronization objects. Polling repeatedly checks the status of an asynchronous call from within a loop. Polling is the least efficient way to manage threads because it wastes resources by repeatedly checking the status of the various thread properties.
For example, the IsAlive property can be used when polling to see if a thread has exited. Use this property with caution because a thread that is alive is not necessarily running. You can use the thread's ThreadState property to get more detailed information about a thread's status. Because threads can be in more than one state at any given time, the value stored in ThreadState can be a combination of the values in the System.Threading.Threadstate enumeration. Consequently, you should carefully check all relevant thread states when polling. For example, if a thread's state indicates that it is not Running, it may be done. On the other hand, it may be suspended or sleeping.
As you can imagine, polling sacrifices some of the advantages of multithreading in return for control over the order that threads run. A more efficient approach would use the Join method to control threads. Join causes a calling procedure to wait either until a thread is done or until the call times out if a timeout is specified. The name, join, is based on the idea that creating a new thread is a fork in the execution path. You use Join to merge separate execution paths into a single thread again.
Figure 1 Threading
One point should be clear: Join is a synchronous or blocking call. Once you call Join or a wait method of a wait handle, the calling procedure stops and waits for the thread to signal that it is done.
Sub JoinThreads() Dim Thread1 As New System.Threading.Thread(AddressOf SomeTask) Thread1.Start() Thread1.Join() ' Wait for the thread to finish. MsgBox("Thread is done") End Sub
These simple ways of controlling threads, which are useful when you are managing a small number of threads, are difficult to use with large projects. The next section discusses some advanced techniques you can use to synchronize threads.
Advanced Synchronization Techniques
Multithreaded applications often use wait handles and monitor objects to synchronize multiple threads. The following table describes some of the .NET Framework classes that can be used when synchronizing threads.
|AutoResetEvent||A wait handle that notifies one or more waiting threads that an event has occurred. AutoResetEvent automatically changes status to signaled when a waiting thread is released.|
|Interlocked||Provides atomic operations for variables that are shared by multiple threads.|
|ManualResetEvent||A wait handle that notifies one or more waiting threads that an event has occurred. The state of a manually reset event remains signaled until the Reset method sets it to the nonsignaled state. Similarly, the state remains nonsignaled until the Set method sets it to the signaled state. Any number of waiting threads, or threads that subsequently begin wait operations for the specified event object by calling one of the wait functions, can be released while the object's state is signaled.|
|Monitor||Provides a mechanism that synchronizes access to objects. Visual Basic .NET applications call SyncLock to use monitor objects.|
|Mutex||A wait handle that can be used for interprocess synchronization.|
|ReaderWriterLock||Defines the lock that implements single-writer and multiple-reader semantics.|
|Timer||Provides a mechanism for running tasks at specified intervals.|
|WaitHandle||Encapsulates operating system-specific objects that wait for exclusive access to shared resources.|
Wait handles are objects that signal the status of one thread to another thread. Threads can use wait handles to notify other threads that they need exclusive access to a resource. Other threads must then wait to use this resource until the wait handle is no longer in use. Wait handles have two states, signaled and nonsignaled. A wait handle that is not owned by any thread is in the signaled state. A wait handle that is owned by a thread is in the nonsignaled state.
Threads request ownership of a wait handle by calling one of the wait methods, such as WaitOne, WaitAny, or WaitAll. The wait methods are blocking calls that are similar to the Join method of an individual thread.
- If no other thread owns the wait handle, the call immediately returns True, the wait handle's status is changed to nonsignaled, and the thread that owns the wait handle continues to run.
- If a thread calls one of a wait handle's wait methods, but the wait handle is owned by another thread, the calling thread will either wait for a specified time (if a timeout is specified) or wait indefinitely (if no timeout is specified) for the other thread to release the wait handle. If a timeout is specified and the wait handle is released before the timeout expires, the call returns True. Otherwise, the call to wait returns False, and the calling thread continues to run.
Threads that own a wait handle call the Set method when they are done or when they no longer need the wait handle. Other threads can reset the status of a wait handle to nonsignaled by either calling the Reset method or by calling WaitOne, WaitAll, or WaitAny and successfully waiting for a thread to call Set. AutoResetEvent handles are automatically reset to nonsignaled by the system after a single waiting thread has been released. If no threads are waiting, the event object's state remains signaled.
|WaitOne||Accepts a wait handle as an argument and causes the calling thread to wait until the current wait handle is signaled by another thread that calls Set.|
|WaitAny||Accepts an array of wait handles as an argument and causes the calling thread to wait until any one of the specified wait handles signal by calling Set.|
|WaitAll||Accepts an array of wait handles as an argument and causes the calling thread to wait until all specified wait handles signal by calling Set.|
|Set||Sets the state of the specified wait handle to signaled and causes any waiting threads to resume.|
|Reset||Sets the state of the specified event to nonsignaled.|
There are three kinds of wait handles commonly used with Visual Basic .NET: mutex objects, ManualResetEvent, AutoResetEvent. The last two are often referred to as synchronization events.
Mutex objects are synchronization objects that can only be owned by a single thread at a time. In fact, the name, mutex, is derived from the fact that ownership of mutex objects is mutually exclusive. Threads request ownership of the mutex object when they require exclusive access to a resource. Because only one thread can own a mutex object at any time, other threads must wait for ownership of a mutex object before using the resource.
The WaitOne method causes a calling thread to wait for ownership of a mutex object. If a thread terminates normally while owning a mutex object, the state of the mutex object is set to signaled and the next waiting thread gets ownership.
Synchronization events are used to notify other threads that something has occurred or that a resource is available. Do not be fooled by the fact that these items use the word event. Synchronization events, which are unlike other Visual Basic events, are really wait handles. Like other wait handles, synchronization events have two states, signaled and nonsignaled. Threads that call one of the wait methods of a synchronization event must wait until another thread signals the event by calling the Set method. There are two synchronization event classes. Threads set the status of ManualResetEvent instances to signaled using the Set method. Threads set the status of ManualResetEvent instances to nonsignaled using the Reset method or when control returns to a waiting WaitOne call. Instances of the AutoResetEvent class can also be set to signaled using Set, but they automatically return to nonsignaled as soon as a waiting thread is notified that the event became signaled.
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) ' Queue a task. AsyncOpDone.WaitOne() ' Wait for the thread to call Set. MsgBox("Thread is done.") End Sub Sub Task(ByVal Arg As Object) MsgBox("Thread is starting.") System.Threading.Thread.Sleep(4000) ' Wait 4 seconds. MsgBox("The state object contains the string " & CStr(Arg)) AsyncOpDone.Set() ' Signal that the thread is done. End Sub End Class
Monitor Objects and SyncLock
Monitor objects are used to ensure that a block of code runs without being interrupted by code running on other threads. In other words, code in other threads cannot run until code in the synchronized code block has finished. The SyncLock keyword is used to simplify access to monitor objects in Visual Basic .NET. Visual C#® .NET uses the Lock keyword in the same way.
Suppose, for example, that you have a program that repeatedly and asynchronously reads data and displays the results. With operating systems that use preemptive multitasking, a running thread can be interrupted by the operating system to allow time for some other thread to run. Without synchronization, it is possible that you could get a partially updated view of the data if the object that represents the data is modified by another thread while the data is being displayed. The SyncLock statement guarantees that a section of code will run without being interrupted. The following example shows how you could use SyncLock to provide the display procedure with exclusive access to a data object.
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 the data. End Sub Sub DisplayResults(ByVal MyDataObject As DataObject) ' Add code to display the results. End Sub
Use SyncLock when you have a section of code that should not be interrupted by code running on a separate thread.
You can use the methods of the Interlocked class to prevent problems that can occur when multiple threads attempt to simultaneously update or compare the same value. The methods of this class let you safely increment, decrement, exchange, and compare values from any thread. The following example shows how to use the Increment method to increment a variable that is shared by procedures running on separate 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
In some cases, you may want to lock a resource only when data is being written and permit multiple clients to simultaneously read data when data is not being updated. The ReaderWriterLock class enforces exclusive access to a resource while a thread is modifying the resource, but it allows non-exclusive access when reading the resource. ReaderWriter locks are a useful alternative to exclusive locks that cause other threads to wait, even when those threads do not need to update data. The following example shows how you can use ReaderWriter to coordinate read and write operations from multiple threads.
Class ReadWrite 'The ReadData and WriteData methods can be safely called from multiple ' threads. Public ReadWriteLock As New System.Threading.ReaderWriterLock() Sub ReadData() ' This procedure reads information from some source. ' The read lock prevents data from being written until the thread ' is done reading, while allowing other threads to call ReadData. ReadWriteLock.AcquireReaderLock(System.Threading.Timeout.Infinite) Try ' Perform read operation here. Finally ReadWriteLock.ReleaseReaderLock() ' Release the read lock. End Try End Sub Sub WriteData() ' This procedure writes information to some source. ' The write lock prevents data from being read or written until ' the thread is done writing. ReadWriteLock.AcquireWriterLock(System.Threading.Timeout.Infinite) Try ' Perform write operation here. Finally ReadWriteLock.ReleaseWriterLock() ' Release the write lock. End Try End Sub End Class
Thread synchronization is invaluable in multithreaded applications, but there is always the danger of deadlocks where multiple threads wait for each other. Analogous to a situation in which cars are stopped at a four-way stop and each person is waiting for another to go, deadlocks bring everything to a halt. Needless to say, avoiding deadlocks is important. There are many ways to get into deadlock situations and almost as many ways to avoid them. Although there is not enough space in this article to cover all the issues related to deadlocks, the important point is that careful planning is key to avoiding them. You can often predict deadlock situations by diagramming multithreaded applications before you start coding.
The Threading.Timer class is useful to periodically run a task on a separate thread. For example, you could use a thread timer to check the status and integrity of a database or to back up critical files. The following example starts a task every two seconds and uses a flag to initiate the Dispose method that stops the timer. This example posts status to the output window, so you should make this window visible by pressing Control+Alt+O before you test the code.
Class StateObjClass ' Used to hold parameters for calls to 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 that calls a procedure every 2 seconds. ' Note: There is no Start method; the timer starts running as soon as ' the instance is created. Dim TimerItem As New System.Threading.Timer(TimerDelegate, StateObj, _ 2000, 2000) StateObj.TimerReference = TimerItem ' Save a reference for Dispose. While StateObj.SomeValue < 10 ' Run for ten loops. System.Threading.Thread.Sleep(1000) ' Wait one second. End While StateObj.TimerCanceled = True ' Request 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("Launched new thread " & Now) If State.TimerCanceled Then ' Dispose Requested. State.TimerReference.Dispose() Debug.WriteLine("Done " & Now) End If End Sub
Thread timers are particularly useful when the System.Windows.Forms.Timer class is unavailable, such as when you are developing console applications.
One advantage of multithreading is that the user interface part of an application continues to be responsive, even when tasks are being performed on other threads. Synchronization events and fields that act as flags are often used to notify another thread that you want it to stop. The following example uses synchronization events to cancel a task. To use this example, add the following module to a project. To start a thread, call the
StartCancel.StartTask() method. To cancel one or more running threads, call the
Module StartCancel Public CancelThread As New System.Threading.ManualResetEvent(False) Public ThreadisCanceled As New System.Threading.ManualResetEvent(False) Private Sub SomeLongTask() Dim LoopCount As Integer Dim Loops As Integer = 10 ' Run code in a While loop until 10 seconds passes, or ' until CancelThread is set. While Not CancelThread.WaitOne(0, False) And LoopCount < Loops ' Do some kind of task here. System.Threading.Thread.Sleep(1000) ' Sleep for one second. LoopCount += 1 End While If CancelThread.WaitOne(0, False) Then 'Acknowledge that the ManualResetEvent CancelThread is set. ThreadisCanceled.Set() MsgBox("Canceling thread") Else MsgBox("Thread is done") End If End Sub Public Sub StartTask() ' Starts a new thread. Dim th As New System.Threading.Thread(AddressOf SomeLongTask) CancelThread.Reset() ThreadisCanceled.Reset() th.Start() MsgBox("Thread Started") End Sub Public Sub CancelTask() ' Stops any threads started by the StartTask procedure. ' Notice that this thread both receives and sends ' synchronization events to coordiante the threads. CancelThread.Set() ' Set CancelThread to ask the thread to stop. If ThreadisCanceled.WaitOne(4000, False) Then ' Wait up to 4 seconds for the thread to ' acknowledge that it has stopped. MsgBox("The thread has stopped.") Else MsgBox("The thread could not be stopped.") End If End Sub End Module
Multithreaded processing is the key to scalable, responsive applications. Visual Basic .NET supports a robust, multithreading development model that enables developers to quickly harness the power of multithreaded applications.
- Visual Basic .NET uses the new .NET Framework classes that make it easy to create multithreaded applications.
- Remember that although multiple threads can improve performance, each thread has an associated cost in terms of additional memory required to create the thread and processor time required to keep it running.
- Thread properties and methods control the interaction among threads and determine when resources are available to running threads.
- Although multithreading may seem to introduce chaos, you can control running threads by using synchronization techniques.
- Multithreading produces scalable applications by allocating available resources efficiently even as application complexity increases.
Using the techniques discussed in this article you can develop professional applications that can handle even the most processor-intensive tasks.
- Guidelines for Asynchronous Programming
- Explains the concept of asynchronous programming and its support in the .NET Framework.
- Delegates, Events, and Remoting
- Explains the concepts of delegates and remoting and how .NET remoting supports asynchronous programming.
- Asynchronous Programming Design Pattern
- Demonstrates asynchronous programming practices by example.
- Remoting Example: Asynchronous Remoting
- Includes a sample that demonstrates asynchronous remoting.