Поделиться через


Пул управляемых потоков

Обновлен: Ноябрь 2007

Класс ThreadPool обеспечивает приложение пулом рабочих потоков, управляемых системой, позволяя пользователю сосредоточиться на выполнении задач приложения, а не на управлении потоками. Если имеются небольшие задачи, которые нуждаются в фоновой обработки, пул управляемых потоков — это самый простой способ воспользоваться преимуществами нескольких потоков.

0ka9477y.alert_note(ru-ru,VS.90).gifПримечание.

Начиная с .NET Framework версия 2.0, с пакетом обновления 1, пропускная способность пула потоков значительно увеличилась в трех ключевых областях, которые считались узкими в предыдущих выпусках .NET Framework: постановка задач в очередь, диспетчеризация потоков в пуле и диспетчеризация потоков завершения ввода и вывода. Для использования этих функциональных возможностей приложение должно использовать .NET Framework, версия 3.5. Дополнительные сведения см. в разделе .Архитектура платформы .NET Framework версии 3.5.

Для фоновых задач, взаимодействующих с пользовательским интерфейсом, .NET Framework версии 2.0 также предоставляет класс BackgroundWorker, который работает с помощью событий, созданных в потоке пользовательского интерфейса.

Платформа .NET Framework использует потоки из пула потоков в различных целях, включая асинхронное завершение ввода и вывода, обратные вызовы таймера, зарегистрированные операции ожидания, асинхронные вызовы методов с использованием делегатов и подключения сокетов System.Net.

Когда не следует использовать потоки из пула потоков

Существует несколько случаев, в которых необходимо создание и управление собственным потоком вместо использования объекта потоков из потоков из пула.

  • Необходимо наличие основного потока.

  • Поток должен иметь определенный приоритет.

  • Имеются задачи, которые приводят к блокировке потока на долгое время. Пул потоков имеет максимальное количество потоков, поэтому большое число заблокированных потоков в пуле потоков может не дать запуститься задачам.

  • Необходимо поместить потоки в однопоточный апартамент. Все потоки ThreadPool находятся в многопоточном апартаменте.are in the multithreaded apartment.

  • Необходимо иметь стабильную идентификацию, сопоставленную с потоком, или назначить поток задаче.

Характеристики пула потоков

Потоки из пула потоков являются фоновыми потоками. См. раздел Основные и фоновые потоки. Для каждого потока используется размер стека по умолчанию, поток запускается с приоритетом по умолчанию и находится в многопоточном апартаменте.

Для каждого процесса существует только один пул потоков.

Исключения в потоках из пула потоков

Необработанные исключения в потоках из пула потоков приводят к завершению процесса. Существует три исключения из этого правила:

  • Исключение ThreadAbortException создается в потоке пула потоков вследствие вызова перегрузки Abort.

  • Исключение AppDomainUnloadedException создается в потоке пула потоков вследствие выгрузки домена приложения.

  • Среда CLR или процесс основного приложения прерывает выполнение потока.

Дополнительные сведения см. в разделе Исключения в управляемых потоках.

0ka9477y.alert_note(ru-ru,VS.90).gifПримечание.

В .NET Framework версии 1.0 и 1.1 среда CLR без оповещения перехватывает все необработанные исключения в потоках из пула потоков. Это может повредить состоянию приложения и в итоге привести к "зависанию" приложений, что может очень усложнить работу по отладке.

Максимальное количество потоков в пуле потоков

Количество операций, которое может быть помещено в очередь пула потоков, ограничено только объемом памяти; однако пул потоков ограничивает количество потоков, которое может быть одновременно активно в процессе. По умолчанию это ограничение составляет 25 рабочих потоков на ЦП и 1000 потоков завершения ввода и вывода.

Можно управлять максимальным количеством потоков с помощью методов GetMaxThreads и SetMaxThreads.

0ka9477y.alert_note(ru-ru,VS.90).gifПримечание.

В .NET Framework версии 1.0 и 1.1 размер пула потоков не может быть задан в управляемом коде. Код, содержащий среду CLR, может задавать этот размер при помощи объекта CorSetMaxThreads, определенного в mscoree.h.

Минимальное количество свободных потоков

Пул потоков также поддерживает минимальное количество доступных потоков, даже если все потоки являются свободными, чтобы задачи в очереди могли начать выполняться незамедлительно. Если количество свободных потоков превышает минимальное, то лишние потоки завершаются для экономии системных ресурсов. По умолчанию для каждого процессора поддерживается по одному свободному потоку.

Пул потоков имеет встроенную задержку (полсекунды в .NET Framework версии 2.0) до запуска новых свободных потоков. Если приложение периодически запускает несколько задач за небольшой промежуток времени, незначительное увеличение количества свободных потоков может привести к значительному увеличению передаваемых данных. Установка слишком высокого значения свободных потоков приведет к ненужному расходу системных ресурсов.

Можно управлять количеством свободных потоков, поддерживаемых пулом потоков, с помощью методов GetMinThreads и SetMinThreads.

0ka9477y.alert_note(ru-ru,VS.90).gifПримечание.

В .NET Framework версии 1.0 невозможно задать минимальное количество свободных потоков.

Пропуск проверок безопасности

Пул потоков также предоставляет методы ThreadPool.UnsafeQueueUserWorkItem и ThreadPool.UnsafeRegisterWaitForSingleObject. Используйте эти методы, только когда есть уверенность, что стек вызывающего объекта не зависит от проверок безопасности, выполненных во время выполнения задачи в очереди. Перегрузки QueueUserWorkItemи RegisterWaitForSingleObject перехватывают стек вызывающего объекта, который объединяется со стеком потока из пула потоков, когда поток начинает выполнять задачу. Если требуется проверка безопасности, проверяется весь стек. Несмотря на обеспечение безопасности, такая проверка влияет также на производительность.

Использование пула потоков

Пул потоков используется путем вызова перегрузки ThreadPool.QueueUserWorkItem из управляемого кода (или CorQueueUserWorkItem из неуправляемого кода) и передачи делегата WaitCallback, представляющего метод, который выполняет задачу. Также можно поместить в очередь рабочие элементы, которые имеют отношение к операции ожидания, посредством вызова метода ThreadPool.RegisterWaitForSingleObject и передачи WaitHandle, который создает вызов метода, представленного делегатом WaitOrTimerCallback, при получении сигнала или истечении времени ожидания. В обоих случаях для вызова метода обратного вызова группа потоков использует фоновый поток.

Примеры ThreadPool

В трех примерах кода, приведенных ниже, показано использование методов QueueUserWorkItem и RegisterWaitForSingleObject.

В первом примере с помощью метода QueueUserWorkItem в очередь помещается простая задача, представленная методом ThreadProc.

Imports System
Imports System.Threading

Public Class Example
    Public Shared Sub Main()
        ' Queue the task.
        ThreadPool.QueueUserWorkItem( _
            New WaitCallback(AddressOf ThreadProc))
        
        Console.WriteLine("Main thread does some work, then sleeps.")
        ' If you comment out the Sleep, the main thread exits before
        ' the thread pool task runs.  The thread pool uses background
        ' threads, which do not keep the application running.  (This
        ' is a simple example of a race condition.)
        Thread.Sleep(1000)

        Console.WriteLine("Main thread exits.")
    End Sub

    ' This thread procedure performs the task.
    Shared Sub ThreadProc(stateInfo As Object)
        ' No state object was passed to QueueUserWorkItem, so 
        ' stateInfo is null.
        Console.WriteLine("Hello from the thread pool.")
    End Sub
End Class
using System;
using System.Threading;
public class Example {
    public static void Main() {
        // Queue the task.
        ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
        
        Console.WriteLine("Main thread does some work, then sleeps.");
        // If you comment out the Sleep, the main thread exits before
        // the thread pool task runs.  The thread pool uses background
        // threads, which do not keep the application running.  (This
        // is a simple example of a race condition.)
        Thread.Sleep(1000);

        Console.WriteLine("Main thread exits.");
    }

    // This thread procedure performs the task.
    static void ThreadProc(Object stateInfo) {
        // No state object was passed to QueueUserWorkItem, so 
        // stateInfo is null.
        Console.WriteLine("Hello from the thread pool.");
    }
}

Обеспечение метода QueueUserWorkItem данными задачи

В следующем примере в очередь добавляется задача с помощью метода QueueUserWorkItem, затем методу предоставляются данные задачи.

Imports System
Imports System.Threading
' TaskInfo holds state information for a task that will be
' executed by a ThreadPool thread.
Public Class TaskInfo
    ' State information for the task.  These members
    ' can be implemented as read-only properties, read/write
    ' properties with validation, and so on, as required.
    Public Boilerplate As String
    Public Value As Integer

    ' Public constructor provides an easy way to supply all
    ' the information needed for the task.
    Public Sub New(text As String, number As Integer)
        Boilerplate = text
        Value = number
    End Sub
End Class

Public Class Example
    Public Shared Sub Main()
        ' Create an object containing the information needed
        ' for the task.
        Dim ti As New TaskInfo("This report displays the number {0}.", 42)

        ' Queue the task and data.
        If ThreadPool.QueueUserWorkItem( _
            New WaitCallback(AddressOf ThreadProc), ti) Then
        
            Console.WriteLine("Main thread does some work, then sleeps.")

            ' If you comment out the Sleep, the main thread exits before
            ' the ThreadPool task has a chance to run.  ThreadPool uses 
            ' background threads, which do not keep the application 
            ' running.  (This is a simple example of a race condition.)
            Thread.Sleep(1000)

            Console.WriteLine("Main thread exits.")
        Else
            Console.WriteLine("Unable to queue ThreadPool request.")
        End If
    End Sub

    ' The thread procedure performs the independent task, in this case
    ' formatting and printing a very simple report.
    '
    Shared Sub ThreadProc(stateInfo As Object)
        Dim ti As TaskInfo = CType(stateInfo, TaskInfo)
        Console.WriteLine(ti.Boilerplate, ti.Value)
    End Sub
End Class
using System;
using System.Threading;

// TaskInfo holds state information for a task that will be
// executed by a ThreadPool thread.
public class TaskInfo {
    // State information for the task.  These members
    // can be implemented as read-only properties, read/write
    // properties with validation, and so on, as required.
    public string Boilerplate;
    public int Value;

    // Public constructor provides an easy way to supply all
    // the information needed for the task.
    public TaskInfo(string text, int number) {
        Boilerplate = text;
        Value = number;
    }
}

public class Example {
    public static void Main() {
        // Create an object containing the information needed
        // for the task.
        TaskInfo ti = new TaskInfo("This report displays the number {0}.", 42);

        // Queue the task and data.
        if (ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), ti)) {    
            Console.WriteLine("Main thread does some work, then sleeps.");

            // If you comment out the Sleep, the main thread exits before
            // the ThreadPool task has a chance to run.  ThreadPool uses 
            // background threads, which do not keep the application 
            // running.  (This is a simple example of a race condition.)
            Thread.Sleep(1000);

            Console.WriteLine("Main thread exits.");
        }
        else {
            Console.WriteLine("Unable to queue ThreadPool request."); 
        }
    }

    // The thread procedure performs the independent task, in this case
    // formatting and printing a very simple report.
    //
    static void ThreadProc(Object stateInfo) {
        TaskInfo ti = (TaskInfo) stateInfo;
        Console.WriteLine(ti.Boilerplate, ti.Value); 
    }
}

Метод RegisterWaitForSingleObject

В следующем примере показаны некоторые возможности потоков.

Imports System
Imports System.Threading

' TaskInfo contains data that will be passed to the callback
' method.
Public Class TaskInfo
    public Handle As RegisteredWaitHandle = Nothing
    public OtherInfo As String = "default"
End Class

Public Class Example
    Public Shared Sub Main()
        ' The main thread uses AutoResetEvent to signal the
        ' registered wait handle, which executes the callback
        ' method.
        Dim ev As New AutoResetEvent(false)

        Dim ti As New TaskInfo()
        ti.OtherInfo = "First task"
        ' The TaskInfo for the task includes the registered wait
        ' handle returned by RegisterWaitForSingleObject.  This
        ' allows the wait to be terminated when the object has
        ' been signaled once (see WaitProc).
        ti.Handle = ThreadPool.RegisterWaitForSingleObject( _
            ev, _
            New WaitOrTimerCallback(AddressOf WaitProc), _
            ti, _
            1000, _
            false _
        )

        ' The main thread waits about three seconds, to demonstrate 
        ' the time-outs on the queued task, and then signals.
        Thread.Sleep(3100)
        Console.WriteLine("Main thread signals.")
        ev.Set()

        ' The main thread sleeps, which should give the callback
        ' method time to execute.  If you comment out this line, the
        ' program usually ends before the ThreadPool thread can execute.
        Thread.Sleep(1000)
        ' If you start a thread yourself, you can wait for it to end
        ' by calling Thread.Join.  This option is not available with 
        ' thread pool threads.
    End Sub
   
    ' 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 Shared Sub WaitProc(state As Object, timedOut As Boolean)
        ' The state object must be cast to the correct type, because the
        ' signature of the WaitOrTimerCallback delegate specifies type
        ' Object.
        Dim ti As TaskInfo = CType(state, TaskInfo)

        Dim cause As String = "TIMED OUT"
        If Not timedOut Then
            cause = "SIGNALED"
            ' If the callback method executes because the WaitHandle is
            ' signaled, stop future execution of the callback method
            ' by unregistering the WaitHandle.
            If Not ti.Handle Is Nothing Then
                ti.Handle.Unregister(Nothing)
            End If
        End If 

        Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.", _
            ti.OtherInfo, _
            Thread.CurrentThread.GetHashCode().ToString(), _
            cause _
        )
    End Sub
End Class
using System;
using System.Threading;

// TaskInfo contains data that will be passed to the callback
// method.
public class TaskInfo {
    public RegisteredWaitHandle Handle = null;
    public string OtherInfo = "default";
}

public class Example {
    public static void Main(string[] args) {
        // The main thread uses AutoResetEvent to signal the
        // registered wait handle, which executes the callback
        // method.
        AutoResetEvent ev = new AutoResetEvent(false);

        TaskInfo ti = new TaskInfo();
        ti.OtherInfo = "First task";
        // The TaskInfo for the task includes the registered wait
        // handle returned by RegisterWaitForSingleObject.  This
        // allows the wait to be terminated when the object has
        // been signaled once (see WaitProc).
        ti.Handle = ThreadPool.RegisterWaitForSingleObject(
            ev,
            new WaitOrTimerCallback(WaitProc),
            ti,
            1000,
            false
        );

        // The main thread waits three seconds, to demonstrate the
        // time-outs on the queued thread, and then signals.
        Thread.Sleep(3100);
        Console.WriteLine("Main thread signals.");
        ev.Set();

        // The main thread sleeps, which should give the callback
        // method time to execute.  If you comment out this line, the
        // program usually ends before the ThreadPool thread can execute.
        Thread.Sleep(1000);
        // If you start a thread yourself, you can wait for it to end
        // by calling Thread.Join.  This option is not available with 
        // thread pool threads.
    }
   
    // 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) {
        // The state object must be cast to the correct type, because the
        // signature of the WaitOrTimerCallback delegate specifies type
        // Object.
        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.
            if (ti.Handle != null)
                ti.Handle.Unregister(null);
        } 

        Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
            ti.OtherInfo, 
            Thread.CurrentThread.GetHashCode().ToString(), 
            cause
        );
    }
}

См. также

Основные понятия

Потоки и работа с потоками

Асинхронный файловый ввод-вывод

Таймеры

Ссылки

ThreadPool

Другие ресурсы

Объекты и функциональные возможности работы с потоками