If you were following my first three parts of this series, you probably are familiar with basics of threading and how to write simple multithreading applications in .NET using the System.Threading.Thread class and implementing thread synchronization on the .NET platform. In this article, I would discuss few more .NET classes and how and what role do they play a role in building multithreading applications. These classes are:
- System.Threading.ThreadPool class
- System.Threading.Timer class
- Calling BeginInvoke against a delegate.
Using the Thread class to create and manage threads is ideal for situations when the number of threads is small and you want to control the thread details like the thread priority etc. For large number of threads, it is always better to consider thread pooling. It provides an efficient thread management mechanism to handle multiple tasks. The Timer class is a flexible way to run tasks at specified intervals. Async method invocation using delegates are also a preferred threading mechanism
System.Threading.ThreadPool Class
As you start creating your own multi-threaded applications, you would realize that for a large part of your time, your threads are sitting idle waiting for something to happen (like a key press or listening for requests at the socket etc). No doubt, as you would also agree, this is an absolute waste of resources.
If there are a number of tasks, each requiring one thread, then you should consider using the ThreadPool class (in the System.Threading namespace) to manage your resources efficiently and to take benefits of multiple threads. Thread pooling is a form of multithreading where tasks are added to a queue and automatically started as threads are created. Using the ThreadPool class enables the system to optimize thread time-slices on the processor. But remember that at any particular point of time there is only one thread pool per process and there is only one working thread per thread pool object. This class provides your application with a pool of worker threads that are managed by the system thus allowing you to concentrate on the workflow logic rather than thread management.
The thread pool is created the first time when you instantiate the ThreadPool class. It has a default limit of 25 threads per available processor, but that can be changed. Thus, the processor is never made to sit idle. If one of the threads is waiting on an event, the thread pool will initiate another worker thread on the processor. The thread pool continues creating worker threads and allocates tasks which are pending in the queue. The only restriction being that the number of worker threads created will never exceed the maximum number allowed. If you create threads that are in addition to the limit specified, then those threads can start only once the currently running threads stop. The number of operations that can be queued to the thread pool is limited only by the available memory. Each thread runs at the default priority and uses the default stack size and is in the multi-threaded apartment. Once a work item has been queued to the thread pool, you cannot cancel it.
You request the thread pool to handle a task (or work items) by calling the QueueUserWorkItem method of the ThreadPool class. This method adds threads to be executed when the processor is free. This method takes as a parameter a WaitCallback delegate (wrapping the object that you want to add to the queue) that will be called by the thread selected from the thread pool. The runtime automatically creates threads for queued thread pool tasks and then releases them when the task is done.
The following code shows how you can create a thread pool. It is a very simple process. Just create a delegate of type WaitCallback and add it to thread pool.
public void afunction(object o)
{
// do what ever the function is supposed to do.
}
//thread entry code
{
.......
// create an instance of WaitCallback
WaitCallback myCallback = new WaitCallback (afunction);
//add this to the thread pool / queue a task
ThreadPool.QueueUserWorkItem (myCallback);
......
}
You can also queue tasks by calling ThreadPool.RegisterWaitForSingleObject method and passing a System.Threading.WaitHandle that when signaled or when timed out raises a call to a method wrapped by the System.Threading.WaitOrTimerCallback delegate.
This mechanism, involving the ThreadPool and an event based programming pattern, facilitates the ThreadPool to monitor all the WaitHandles registered with it and then calling the appropriate WaitOrTimerCallback delegate method when the WaitHandle is released. The modus operandi of this is very simple. There is one thread that constantly observes the status of the wait operations queued to the thread pool. When the wait operation completes, a worker thread from the pool executes the corresponding callback function. Thus, this method adds a new thread with a triggering event.
Let us see how we can add a new thread with an event to the thread pool. Doing this is very simple. We need to create an event, which we will do with the ManualResetEvent class. We next need to create a WaitOrTimerCallback delegate. Then we need an object that will carry the status info to the delegate. We have also to determine timeout interval and the execution style. We will add all of them to the thread pool and set the event on fire.
public void afunction(object o)
{
// do what ever the function is supposed to do.
}
//object that will carry the status info...
public class anObject
{
.....
}
//thread entry code
{
.....
//create an event object
ManualResetEvent aevent = new ManualResetEvent (false);
// create an instance of WaitOrTimerCallback
WaitOrTimerCallback thread_method = new WaitOrTimerCallback (afunction);
// create an instance of anObject
anObject myobj = new anObject();
// decide how thread will perform
int timeout_interval = 100; // timeout in milli-seconds.
bool onetime_exec = true;
//add all this to the thread pool.
ThreadPool. RegisterWaitForSingleObject (aevent, thread_method, myobj, timeout_interval, onetime_exec);
// raise the event
aevent.Set();
.....
}
In both the QueueUserWorkItem and RegisterWaitForSingleObject methods, the thread pool creates a background thread to invoke the callback method. When the thread pool thread starts to execute a task, both the methods merge the caller's stack into the stack of the thread pool thread. If a security check is needed, the entire stack is therefore checked. This of course consumes time and hence has an added performance price attached to it. You can avoid the security checks (that provides better performance, though less safety) by using the unsafe counterparts of these methods.
The unsafe methods ThreadPool.UnsafeRegisterWaitForSingleObject and ThreadPool.UnsafeQueueUserWorkItem can be used instead.
You can also queue work items that are not related to a wait operation to the thread pool. Timer-queue timers and registered wait operations also use the thread pool. Their callback functions are queued to the thread pool.
Thread pooling is a very useful concept and is widely used in the .Net platform to make socket connections, registered wait operations, process timers or in asynchronous I/O completion. For small and short tasks, the thread pooling mechanism is a convenient way to handle multiple threads. This concept is beneficial when you want to start many separate tasks without individually setting the properties of each thread.
But, you should also remember that there are some situations where it is suitable to create and manage your own threads instead of using the ThreadPool class. Cases where you would want to schedule a task (give a particular priority level to a thread) or give a thread a particular identity (like a name to the thread) or if you feel the need to place the threads in a single-threaded apartment (ThreadPool places the threads in a multithreaded apartment) or if you know that a particular task is lengthy (hence chances that it might block other threads is high), you might feel safe in creating your own threads rather than using threads from the thread pool.
System.Threading.Timer Class
The Timer class (in the System.Threading namespace) is effective to periodically run a task on a separate thread. It provides a way to execute methods at specified intervals. This class cannot be inherited.
This class is particularly useful for developing console applications, where the System.Windows.Forms.Timer class is inaccessible. Thus, we can use a thread timer to back up files, or to ensure the consistency of a database.
When creating a timer object, you would need to assess the time to wait before the first invocation of the delegate method (dueTime) and the time to wait between succeeding invocations (period). A timer invokes its methods when its due time elapses, and invokes its methods once per period thereafter. You can use the Change method of the Timer class to change these values, or disable the timer. You can use the Dispose method of the Timer class to free the resources when a timer is no longer needed.
The TimerCallback delegate specifies the methods associated with a Timer object and handles its state. It invokes its methods once after the dueTime elapses and continues invoking its methods once per period until the Dispose method is called. The system automatically allocates a separate thread on which to execute the methods associated with the delegate.
Let us see a small piece of code where we would create a Timer object and use it. We will first need to create a TimerCallback delegate with a method that would be called. Next we will need to create a state object (if one is needed) that contains application specific info relevant to the methods invoked by the delegate. To put things simple, we pass a null reference here. We will then instantiate a Timer object (where we pass the delegate, state object and two more parameters that define the dueTime and period of the Timer). We will use the Change method to change the Timer object settings. Finally, we will kill the Timer object by calling the Dispose method.
// class that will be called by the Timer
public class WorkonTimerReq
{
public void aTimerCallMethod()
{
// does some work .....
}
}
//timer creation block
{
//instantiating the class that gets called by the Timer.
WorkonTimerReq anObj = new WorkonTimerReq () ;
// callback delegate
TimerCallback tcallback = new TimerCallback(anObj. aTimerCallMethod) ;
// define the dueTime and period
long dTime = 20 ; // wait before the first tick (in ms)
long pTime = 150 ; // timer during subsequent invocations (in ms)
// instantiate the Timer object
Timer atimer = new Timer(tcallback, null, dTime, pTime) ;
// do some thing with the timer object
...
//change the dueTime and period of the Timer
dTime=100;
pTime=300;
atimer.Change(dTime, pTime) ;
// do some thing
...
atimer.Dispose() ;
...
}
Asynchronous Programming
This is a topic in itself and needs detailed explanation. Here, we would just get some familiarity as to what it is because it would not do justice to the multi-threading concepts by ignoring this topic, as asynchronous programming is another way of introducing threads in your application.
We have so far studied what synchronous calls are and how they are invoked. But, these suffer an intrinsic shortcoming, which you might have also noticed. The thread that makes a synchronous call is blocked and has to wait until the function completes. Of course, in some situations this is adequate as in the case where your program's logic is dependent on whatever the thread is doing (say reading the values from the database without which the program cannot proceed).
Asynchronous programming allows more flexibility. A thread that makes an asynchronous call is not blocked on that call. You can use the thread to do almost any task and when the call completes, the thread picks up the results and proceeds. This concept gives a better scalability to an enterprise wide system where the threads might have to manage numerous call requests and hence cannot afford to wait on each request.
The .NET platform provides a consistent support for asynchronous programming and this aspect is used in ASP.NET web forms, I/O operations, Web Services, networking, messaging etc. One of the main design patterns that have made asynchronous programming possible is the .NET delegate classes.
Asynchronous delegates
Asynchronous delegates provide the facility to call a synchronous method in an asynchronous manner. In case of a synchronous call, the compiler calls the invoke method that directly calls the target method on the current thread. In case of asynchronous call, rather than calling the target method directly, the compiler creates the type-safe methods BeginInvoke and EndInvoke. The moment the BeginInvoke is called, CLR will queue the request and return to the caller instantly. The target method would be called on a thread from the thread pool. The original thread that had submitted the request is thus free to carry out any other activities. If you specify a callback on the BeginInvoke method then the target method calls it, when it returns. In the callback, the EndInvoke method is used to obtain the results. If you do not specify a callback on the BeginInvoke, then you have to use the EndInvoke method on the thread that had submitted the request.
We have already studied the ThreadPool and ReaderWriterLock classes. The Common Language Runtime supports asynchronous programming model via these classes. The synchronization constructs like the WaitForMultipleObjects method also support the asynchronous programming feature.
As a developer, you will have to evaluate the merits and demerits of the synchronous calls to those of the asynchronous types. Synchronous calls are easy to code and offer complete safety with the help of the different synchronization objects available. Asynchronous calls are a must in a large-scale applications to reduce the number and complexity of threads but are mainly implemented through polling. Polling is not a very proficient way of administering threads as it unnecessary consumes a lot of resources by continually checking the status of an asynchronous call from within a loop.
Conclusion
This brings us to an end on the multithreading concepts. It is a vast topic and you need to create your own web of threads to understand it. Just remember that although performance level is boosted by creating multiple threads, yet thread creation is an expensive proposition in terms of the memory required and CPU time consumed to keep them running. Stay away from creating too many useless threads and assigning incorrect priorities to them.