The Managed Thread Pool

The ThreadPool class provides your application with a pool of worker threads that are managed by the system, allowing you to concentrate on application tasks instead of thread management. If you have tasks that require background processing, the managed thread pool is an easy way to take advantage of multiple threads.

The .NET Framework uses thread pool threads for many purposes, including asynchronous I/O completion, timer callbacks, registered wait operations, System.Net socket connections, and tasks performed by the BackgroundWorker class.

For background tasks that interact with the user interface (UI), the BackgroundWorker class provides an easy way to use thread pool threads, because it communicates by raising events on the UI thread. See How to: Use a Background Worker and Synchronizing Data for Multithreading.

In the following scenarios, it is appropriate to create and manage your own threads instead of using thread pool threads:

  • You need to start a number of threads in a very short time, to execute tasks that last a second or more. As part of its thread management strategy, the thread pool delays before creating threads. Therefore, when a number of tasks are queued in a short period of time, there can be a significant delay before all the tasks are started.

  • You have many tasks that cause the thread to block for long periods of time. The thread pool has a maximum number of threads, so a large number of blocked thread pool threads might prevent tasks from starting.

  • You need to have a stable identity associated with a thread, or you need to dedicate a thread to a task.

There is only one thread pool per process.

Thread pool threads are background threads. Each thread uses the default stack size, runs at the default priority, and is in the multithreaded apartment.

Note Note:

In Silverlight, there is no difference in behavior between foreground and background threads.

Exceptions in Thread Pool Threads

Unhandled exceptions on thread pool threads terminate the process. There are three exceptions to this rule:

  • A ThreadAbortException is thrown in a thread pool thread, because Abort was called.

  • An AppDomainUnloadedException is thrown in a thread pool thread, because the application domain is being unloaded.

  • The common language runtime or a host process terminates the thread.

For more information, see Exceptions in Managed Threads.

Maximum Number of Thread Pool Threads

The number of operations that can be queued to the thread pool is limited only by available memory; however, the thread pool limits the number of threads that can be active in the process simultaneously. By default, the limit is 250 worker threads per CPU and 1,000 I/O completion threads.

Minimum Number of Idle Threads

The thread pool also maintains a minimum number of available threads, even when all threads are idle, so that queued tasks can start immediately. Idle threads in excess of this minimum are terminated to save system resources. By default, one idle thread is maintained per processor.

You use the thread pool by calling the ThreadPool.QueueUserWorkItem method and passing a WaitCallback delegate representing the method that performs the task. You can also queue work items that are related to a wait operation by using the ThreadPool.RegisterWaitForSingleObject method and passing a WaitHandle that, when signaled or when timed out, raises a call to the method represented by the WaitOrTimerCallback delegate. In both cases, the thread pool uses a background thread to invoke the callback method.

The three examples that follow demonstrate the QueueUserWorkItem and RegisterWaitForSingleObject methods.

The first example queues a very simple task, represented by the ThreadProc method, by using the QueueUserWorkItem method. No task information is supplied with this overload. Therefore, the information that is available to the ThreadProc method is limited to the object the method belongs to.

The example displays its output in a TextBlock on the UI thread. To access the TextBlock from the callback thread, the example uses the Dispatcher property to obtain a Dispatcher object for the TextBlock, and then uses the Dispatcher.BeginInvoke method to make the cross-thread call.


using System;
using System.Threading;

class Example
{
    // This is the UI element that receives the output from the example.
    private static System.Windows.Controls.TextBlock outputBlock;

    // The Demo method runs the example. It saves the TextBlock that is 
    // used for output, and sets up an event handler to start tasks.
    public static void Demo(System.Windows.Controls.TextBlock outputBlock)
    {
        Example.outputBlock = outputBlock;
        outputBlock.Text = "Click here to start a background task.\r\n";

        // Set up an event handler to start a task when the TextBlock 
        // is clicked.
        outputBlock.MouseLeftButtonUp += HandleMouseUp;
    }

    // Clicking the TextBlock queues a delegate to perform a task on a 
    // thread pool thread.
    private static void HandleMouseUp(object sender, 
                                      System.Windows.Input.MouseButtonEventArgs e)
    {
        ThreadPool.QueueUserWorkItem(AppendTextTask);

        // Note: The stateInfo object for the task in this example is
        // Nothing, because this overload of QueueUserWorkItem does not
        // take a stateInfo parameter. 
    }

    // This method performs the task, which is to append text to the
    // TextBlock. To communicate with objects on the UI thread, get the 
    // Dispatcher for one of the UI objects. Use the Dispatcher object's 
    // BeginInvoke method to queue a delegate that will run on the UI thread,
    // and therefore can safely access UI elements like the TextBlock.
    private static void AppendTextTask(object stateInfo)
    {
        outputBlock.Dispatcher.BeginInvoke(delegate () {
            outputBlock.Text += "Hello from the thread pool.\r\n";
        });
    }
}

/* This code produces output similar to the following:

Click here to start a background task.
Hello from the thread pool.
Hello from the thread pool.
Hello from the thread pool.
 */


Supplying Task Data for QueueUserWorkItem

The following example uses the QueueUserWorkItem method to queue a task and supply the data for the task.

The example displays its output in a TextBlock on the UI thread. To access the TextBlock from the callback thread, the example uses the Dispatcher property to obtain a Dispatcher object for the TextBlock, and then uses the Dispatcher.BeginInvoke method to make the cross-thread call.


using System;
using System.Threading;

class Example
{
    // This is the UI element that receives the output from the example.
    private static System.Windows.Controls.TextBlock outputBlock;

    // The Demo method runs the example. It saves the TextBlock that is 
    // used for output, and sets up an event handler to start tasks.
    public static void Demo(System.Windows.Controls.TextBlock outputBlock)
    {
        Example.outputBlock = outputBlock;
        outputBlock.Text = "Click here to start a background task.\r\n";

        // Set up an event handler to start a task when the TextBlock 
        // is clicked.
        outputBlock.MouseLeftButtonUp += HandleMouseUp;
    }

    // Clicking the TextBlock queues a delegate to perform a task on a 
    // thread pool thread. The data passed to the task is an object 
    // representing the time the task was queued.
    private static void HandleMouseUp(object sender, 
                                      System.Windows.Input.MouseButtonEventArgs e)
    {
        ThreadPool.QueueUserWorkItem(AppendTextTask, DateTime.Now);
    }

    // This method performs the task, which is to append text to the
    // TextBlock. To communicate with objects on the UI thread, get the 
    // Dispatcher for one of the UI objects. Use the Dispatcher object's 
    // BeginInvoke method to queue a delegate that will run on the UI thread,
    // and therefore can safely access UI elements like the TextBlock.
    private static void AppendTextTask(object stateInfo)
    {
        outputBlock.Dispatcher.BeginInvoke(delegate () {
            outputBlock.Text += String.Format(
                "Hello from the thread pool. Task queued at: {0}\r\n",
                stateInfo);
        });
    }
}

/* This code produces output similar to the following:

Click here to start a background task.
Hello from the thread pool. Task queued at: 6/30/2008 4:41:54 PM
Hello from the thread pool. Task queued at: 6/30/2008 4:42:03 PM
Hello from the thread pool. Task queued at: 6/30/2008 4:42:03 PM
 */


RegisterWaitForSingleObject

The following example demonstrates several threading features:

The example displays its output in a TextBlock on the UI thread. To access the TextBlock from the callback thread, the example uses the Dispatcher property to obtain a Dispatcher object for the TextBlock, and then uses the Dispatcher.BeginInvoke method to make the cross-thread call.


using System;
using System.Threading;

// TaskInfo contains data that will be passed to the callback 
// method.
public class TaskInfo
{
   public RegisteredWaitHandle Handle = null;
   public System.Windows.Controls.TextBlock OutputBlock = null;
}

public class Example
{
    // The Demo method runs the example. It sets up an event handler to 
    // signal the wait handle, saves the TextBlock that is  used for 
    // output, and finally registers the wait handle.
    public static void Demo(System.Windows.Controls.TextBlock outputBlock)
    {
        outputBlock.Text = "Click here signal the wait handle.\r\n";

        // Create the wait handle that the example waits on.
        AutoResetEvent ev = new AutoResetEvent(false);

        // Set up an event handler to signal the wait handle when the 
        // TextBlock is clicked.
        outputBlock.MouseLeftButtonUp += (object sender, 
            System.Windows.Input.MouseButtonEventArgs e) => ev.Set();

        // Create a TaskInfo and save the TextBlock that the example uses
        // for output.
        TaskInfo ti = new TaskInfo();
        ti.OutputBlock = outputBlock;

        // Set the Handle property of the TaskInfo to the registered wait
        // handle that is returned by RegisterWaitForSingleObject. This 
        // enables the wait to be terminated when the handle has been 
        // signaled once (see WaitProc).
        ti.Handle = ThreadPool.RegisterWaitForSingleObject(
            ev,
            new WaitOrTimerCallback(WaitProc),
            ti,
            1000,
            false
        );
    }

    // The callback method executes when the registered wait times out,
    // or when the WaitHandle (in this case AutoResetEvent) is signaled.
    // WaitProc unregisters the WaitHandle the first time the event is 
    // signaled.
    public static void WaitProc(object state, bool timedOut)
    {
        TaskInfo ti = (TaskInfo)state;

        string cause = "TIMED OUT";
        if (!timedOut)
        {
            cause = "SIGNALED";
            // If the callback method executes because the WaitHandle is
            // signaled, stop future execution of the callback method
            // by unregistering the WaitHandle.
            ti.Handle.Unregister(null);
        }

        ti.OutputBlock.Dispatcher.BeginInvoke(delegate () {
            ti.OutputBlock.Text += 
                String.Format("WaitProc is running; cause = {0}.\n", cause);
        });
    }
}

/* This example produces output similar to the following:

Click here to signal the wait handle.
WaitProc is running; cause = TIMED OUT.
WaitProc is running; cause = TIMED OUT.
WaitProc is running; cause = TIMED OUT.
WaitProc is running; cause = SIGNALED.
 */


Community Additions

ADD
Show: