The ThreadPool Class

Many threads spend most of their time waiting for user interaction, such as a user logging in, a key being pressed, or the availability of data on an I/O stream. In its ThreadPool class, .NET has provided an event-driven model that can replace these threads.

The .NET ThreadPool class has a fixed number of threads available for each processor—normally 50 (25 worker threads and 25 threads for asynchronous I/O). This number is fixed by the CLR and cannot be changed. This pool of threads can monitor several wait operations and notify a delegate when they become free.

Explicit Thread Alternative

The ThreadPool class can be used to offload an asynchronous task without using a thread directly. This is not as useful as it sounds since one of the available threads from the ThreadPool will be assigned the task and there are only 25 threads available for tasks in the first place. Furthermore, additional Thread-Pools cannot be created, and any code running in the process space has equal access to this resource. We can see some advantages to this approach, but since there is so little control over the way that work is handled inside a ThreadPool, we think a low-priority thread is usually a better approach. Nonetheless, for completeness, we include an example of how to use this feature:

class ThreadPoolExample {

    public ThreadPoolExample() {
        ThreadPool.QueueUserWorkItem(
            new WaitCallback(doCount), "mystate");
    }

    public void doCount(object p_status) {
        for (int i = 0; i < 10; i++) {
            Console.WriteLine("Count: " + i);
        }
    }
}

The WaitCallBack delegate is used to pass in the method that should be executed. The method signature requires a state object that can be used to differentiate between different operations. The call to QueueUserWorkItem causes the request to be added to the queue immediately, although the elapsed time before the delegate is called is driven by the workload of the pool.

Important

Once a request has been queued, it cannot be stopped or canceled.

While this approach can be used to achieve threading without having to create the threads manually, we think that this achieves short-term simplicity at a high long-term price.

Waiting for an Event

We feel that waiting for an event is the real strength of the ThreadPool class. It’s possible to register a callback for a delegate that will be signaled when an event is triggered. We find ourselves using this for all sorts of tasks, especially for handling logging and auditing operations. The following is a simple example:

class ThreadPoolExample {

    public ThreadPoolExample() {
        // create the reset event
        AutoResetEvent x_event = new AutoResetEvent(false);
        WaitOrTimerCallback x_callback
            = new WaitOrTimerCallback(this.CallbackHandler);
        object x_status = "my status";

        // add to thread pool
        ThreadPool.RegisterWaitForSingleObject(x_event, x_callback,
            x_status, -1, false);

        x_event.Set();
    }

    public void CallbackHandler(object p_status, bool p_timeout) {
        Console.WriteLine("STAT: " + p_status);
        Console.WriteLine("SIG: " + p_timeout);
    }
}

The first step is to create an AutoResetEvent instance. This is an event that will reset after being signaled and is the key to triggering the threaded code. As with most .NET threading operations, we are required to pass in a delegate to the WaitOrTimerCallback constructor, defining the method that will be called when the event is signaled and taking an object argument (which contains the user-defined state) and a bool that will be true if the timer expires before the code is triggered.

The last step is to register with the ThreadPool. We do this by calling RegisterWaitForSingleObject, passing in the event, the callback delegate, the status object that we want passed to the delegate (in this case a String), a timeout value, and a bool indicating whether the event should be reset after it’s signaled.

In our example, we specify a timeout of -1, meaning that nothing should be done until the event is signaled. There are overloaded forms of this method that take various sizes of timeout and one that takes a System.TimeSpan.

We then set the event signal. This would be done in response to something changing (a user clicking a button, a new connection, and so forth), but in our example, we simply set the signal after registering with the ThreadPool. The effect of this is that our CallbackHandler method will be called immediately.

If we had set the timeout to be greater than -1, we would receive periodic notification to indicate that the timer had expired (indicated by the bool in the delegate signature).

The real power of this class is that the code can be notified if the event isn’t fired within a given time period. The addition of the state object means that one handler can be responsible for processing the events for several registrations, thus providing further flexibility.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset