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


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

Класс ThreadPool обеспечивает приложение пулом рабочих потоков, управляемых системой, позволяя пользователю сосредоточиться на выполнении задач приложения, а не на управлении потоками. Если имеются небольшие задачи, которые нуждаются в фоновой обработки, пул управляемых потоков — это самый простой способ воспользоваться преимуществами нескольких потоков. Например, начиная с .NET Framework 4, можно создавать объекты Task и Task<TResult>, выполняющие асинхронные задачи в потоках из пула потоков.

ПримечаниеПримечание

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ПримечаниеПримечание

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

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

Количество операций, которое может быть помещено в очередь пула потоков, ограничено только объемом памяти; однако пул потоков ограничивает количество потоков, которое может быть одновременно активно в процессе. Начиная с .NET Framework 4, размер по умолчанию пула потоков для процесса зависит от нескольких факторов, таких как размер виртуального адресного пространства. Процесс может вызвать метод GetMaxThreads для определения количества потоков.

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

ПримечаниеПримечание

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

Минимальные значения пула потоков

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

ПримечаниеПримечание

Если потребность низкая, фактическое количество потоков из пула потоков может сократиться ниже минимальных значений.

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

Предупреждающее замечаниеВнимание

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

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

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

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

Начиная с .NET Framework 4, самым простым способом использования пула потоков является использование Библиотека параллельных задач. По умолчанию такие типы параллельных библиотек, как Task и Task<TResult> используют потоки из пула потоков для выполнения задач. Пул потоков также можно использовать путем вызова перегрузки ThreadPool.QueueUserWorkItem из управляемого кода (или CorQueueUserWorkItem из неуправляемого кода) и передачи делегата WaitCallback, представляющего метод, который выполняет задачу. Другим способом использования пула потоков является помещение в очередь рабочих элементов, которые имеют отношение к операции ожидания, путем использования метода ThreadPool.RegisterWaitForSingleObject и передачи дескриптора WaitHandle, который вызывает метод, представленный делегатом WaitOrTimerCallback, при получении сигнала или истечении времени ожидания. Потоки из пула потоков используются для вызова методов обратного вызова.

Примеры ThreadPool

В примерах кода в этом разделе пул потоков демонстрируется путем использования класса Task, метода ThreadPool.QueueUserWorkItem и метода ThreadPool.RegisterWaitForSingleObject.

  • Выполнение асинхронных задач с помощью библиотеки параллельных задач

  • Асинхронное выполнение кода с помощью QueueUserWorkItem

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

  • Использование RegisterWaitForSingleObject

Выполнение асинхронных задач с помощью библиотеки параллельных задач

В следующем примере показан способ создания и использования объекта Task путем вызова метода TaskFactory.StartNew. Пример, в котором для возвращения значения из асинхронной задачи используется класс Task<TResult>, можно найти в разделе Практическое руководство. Возвращение значения из задачи.

Imports System.Threading
Imports System.Threading.Tasks
Module StartNewDemo

    ' Demonstrated features:
    '   Task ctor()
    '   Task.Factory
    '   Task.Wait()
    '   Task.RunSynchronously()
    ' Expected results:
    '   Task t1 (alpha) is created unstarted.
    '   Task t2 (beta) is created started.
    '   Task t1's (alpha) start is held until after t2 (beta) is started.
    '   Both tasks t1 (alpha) and t2 (beta) are potentially executed on threads other than the main thread on multi-core machines.
    '   Task t3 (gamma) is executed synchronously on the main thread.
    ' Documentation:
    '   https://msdn.microsoft.com/en-us/library/system.threading.tasks.task_members(VS.100).aspx
    Private Sub Main()
        Dim action As Action(Of Object) = Sub(obj As Object)
                                              Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId)
                                          End Sub

        ' Construct an unstarted task
        Dim t1 As New Task(action, "alpha")

        ' Cosntruct a started task
        Dim t2 As Task = Task.Factory.StartNew(action, "beta")

        ' Block the main thread to demonstate that t2 is executing
        t2.Wait()

        ' Launch t1 
        t1.Start()

        Console.WriteLine("t1 has been launched. (Main Thread={0})", Thread.CurrentThread.ManagedThreadId)

        ' Wait for the task to finish.
        ' You may optionally provide a timeout interval or a cancellation token
        ' to mitigate situations when the task takes too long to finish.
        t1.Wait()

        ' Construct an unstarted task
        Dim t3 As New Task(action, "gamma")

        ' Run it synchronously
        t3.RunSynchronously()

        ' Although the task was run synchrounously, it is a good practice to wait for it which observes for 
        ' exceptions potentially thrown by that task.
        t3.Wait()
    End Sub


End Module
using System;
using System.Threading;
using System.Threading.Tasks;

class StartNewDemo
{
    // Demonstrated features:
    //      Task ctor()
    //      Task.Factory
    //      Task.Wait()
    //      Task.RunSynchronously()
    // Expected results:
    //      Task t1 (alpha) is created unstarted.
    //      Task t2 (beta) is created started.
    //      Task t1's (alpha) start is held until after t2 (beta) is started.
    //      Both tasks t1 (alpha) and t2 (beta) are potentially executed on threads other than the main thread on multi-core machines.
    //      Task t3 (gamma) is executed synchronously on the main thread.
    // Documentation:
    //      https://msdn.microsoft.com/en-us/library/system.threading.tasks.task_members(VS.100).aspx
    static void Main()
    {
        Action<object> action = (object obj) =>
        {
            Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);
        };

        // Construct an unstarted task
        Task t1 = new Task(action, "alpha");

        // Cosntruct a started task
        Task t2 = Task.Factory.StartNew(action, "beta");

        // Block the main thread to demonstate that t2 is executing
        t2.Wait();

        // Launch t1 
        t1.Start();

        Console.WriteLine("t1 has been launched. (Main Thread={0})", Thread.CurrentThread.ManagedThreadId);

        // Wait for the task to finish.
        // You may optionally provide a timeout interval or a cancellation token
        // to mitigate situations when the task takes too long to finish.
        t1.Wait();

        // Construct an unstarted task
        Task t3 = new Task(action, "gamma");

        // Run it synchronously
        t3.RunSynchronously();

        // Although the task was run synchrounously, it is a good practice to wait for it which observes for 
        // exceptions potentially thrown by that task.
        t3.Wait();
    }


}

Асинхронное выполнение кода с помощью QueueUserWorkItem

В следующем примере с помощью метода 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.");
    }
}
using namespace System;
using namespace System::Threading;

public ref class Example
{
public:
    static void Main()
    {
        // Queue the task.
        ThreadPool::QueueUserWorkItem(gcnew 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.");
    }
};

int main()
{
    Example::Main();
}

Обеспечение метода 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);
    }
}
using namespace System;
using namespace System::Threading;

// TaskInfo holds state information for a task that will be
// executed by a ThreadPool thread.
public ref 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;
    int Value;

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

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

        // Queue the task and data.
        if (ThreadPool::QueueUserWorkItem(gcnew 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);
    }
};

int main()
{
    Example::Main();
}

Использование 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
        );
    }
}
using namespace System;
using namespace System::Threading;

// TaskInfo contains data that will be passed to the callback
// method.
public ref class TaskInfo
{
public:
    static RegisteredWaitHandle^ Handle = nullptr;
    static String^ OtherInfo = "default";
};

public ref class Example
{
public:
    static void Main()
    {
        // The main thread uses AutoResetEvent to signal the
        // registered wait handle, which executes the callback
        // method.
        AutoResetEvent^ ev = gcnew AutoResetEvent(false);

        TaskInfo^ ti = gcnew 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,
            gcnew 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.
    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 != nullptr)
                ti->Handle->Unregister(nullptr);
        }

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

int main()
{
    Example::Main();
}

См. также

Задачи

Практическое руководство. Возвращение значения из задачи

Ссылки

ThreadPool

Task

Task<TResult>

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

Библиотека параллельных задач

Библиотека параллельных задач

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

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

таймеры

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

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

Журнал изменений

Дата

Журнал

Причина

Сентябрь 2010

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

Исправление ошибки содержимого.