Асинхронное программирование на основе задач

Библиотека параллельных задач (TPL) основана на концепции задач, представляющих асинхронные операции. В некотором смысле задача похожа на поток или рабочий элемент ThreadPool, но на более высоком уровне абстракции. Термин параллелизм задач означает одновременное выполнение одной или нескольких разных задач. Задачи предоставляют два основных преимущества.

  • Более эффективное и масштабируемое использование системных ресурсов.

    В фоновом режиме задачи помещаются в очередь ThreadPool, усовершенствованную с помощью алгоритмов, которые определяют и настраивают количество потоков и обеспечивают балансировку нагрузки для повышения производительности. Это делает задачи относительно простыми и позволяет создавать множество задач для использования точного параллелизма.

  • Больший программный контроль по сравнению с потоком или рабочим элементом.

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

По этим причинам TPL в .NET — предпочтительный API для написания многопоточного, асинхронного и параллельного кода.

Неявное создание и запуск задач

Метод Parallel.Invoke предоставляет удобный способ одновременного запуска любого числа произвольных операторов. Достаточно передать в делегат Action для каждого рабочего элемента. Самым простым способом создания этих делегатов является использование лямбда-выражений. Лямбда-выражение может вызвать именованный метод или предоставить встроенный код. В следующем примере показан вызов базового метода Invoke, который создает и запускается две задачи, выполняемые параллельно. Первая задача представляется лямбда-выражением, вызывающим метод DoSomeWork, а вторая — лямбда-выражением, вызывающим метод DoSomeOtherWork.

Примечание

В этой документации для определения делегатов в библиотеке параллельных задач используются лямбда-выражения. Если вы не знакомы с лямбда-выражениями в C# или Visual Basic, см. раздел Лямбда-выражения в PLINQ и TPL.

Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());
Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())

Примечание

Число экземпляров Task, созданных Invoke в фоновом режиме, не обязательно равно числу предоставленных делегатов. Библиотека параллельных задач может применять различные оптимизации, особенно с большим количеством делегатов.

Дополнительные сведения см. в разделе Практическое руководство. Использование функции Parallel.Invoke для выполнения параллельных операций.

Для большего контроля над выполнением задач или возврата значения из задачи необходимо более явно работать с объектами Task.

Явное создание и запуск задач

Задача, не возвращающая значение, представляется классом System.Threading.Tasks.Task. Задача, возвращающая значение, представляется классом System.Threading.Tasks.Task<TResult>, унаследованным от Task. Объект задачи обрабатывает сведения инфраструктуры и предоставляет методы и свойства, доступные из вызывающего потока в течение времени существования задачи. Например, можно получить доступ к свойству Status задачи в любое время для определения того, было ли начато ее выполнение, завершилась ли она, была ли отменена или создала исключение. Состояние представлено перечислением TaskStatus.

При создании задачи ей передается пользовательский делегат, инкапсулирующий код, который будет выполнять задача. Делегат может быть выражен как именованный делегат, анонимный метод или лямбда-выражение. Лямбда-выражения могут содержать вызов именованного метода, как показано в следующем примере. Обратите внимание, что в пример включен вызов метода Task.Wait, чтобы убедиться в окончании выполнения задачи до завершения работы приложения консольного режима.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Create a task and supply a user delegate by using a lambda expression.
      Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));
      // Start the task.
      taskA.Start();

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                        Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output like the following:
//       Hello from thread 'Main'.
//       Hello from taskA.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Thread.CurrentThread.Name = "Main"

        ' Create a task and supply a user delegate by using a lambda expression. 
        Dim taskA = New Task(Sub() Console.WriteLine("Hello from taskA."))
        ' Start the task.
        taskA.Start()

        ' Output a message from the calling thread.
        Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name)
        taskA.Wait()
    End Sub
End Module
' The example displays output like the following:
'    Hello from thread 'Main'.
'    Hello from taskA.

Для создания и запуска задачи в одной операции можно также использовать методы Task.Run. Для управления задачей методы Run используют планировщик задач по умолчанию независимо от того, какой планировщик связан с текущим потоком. Методы Run — предпочтительный способ создания и запуска задач, если не требуется более жесткий контроль над созданием и планированием задачи.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Define and run the task.
      Task taskA = Task.Run( () => Console.WriteLine("Hello from taskA."));

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output like the following:
//       Hello from thread 'Main'.
//       Hello from taskA.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Thread.CurrentThread.Name = "Main"

        Dim taskA As Task = Task.Run(Sub() Console.WriteLine("Hello from taskA."))

        ' Output a message from the calling thread.
        Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name)
        taskA.Wait()
    End Sub
End Module
' The example displays output like the following:
'    Hello from thread 'Main'.
'    Hello from taskA.

Для создания и запуска задачи в одной операции можно также использовать метод TaskFactory.StartNew. Используйте этот метод, если нет необходимости разделять создание и планирование и требуются дополнительные параметры создания задач или использование определенного планировщика, а также при необходимости передачи дополнительного состояния задаче, которое можно получить через ее свойство Task.AsyncState, как показано в следующем примере.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null)
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);
      foreach (var task in taskArray) {
         var data = task.AsyncState as CustomData;
         if (data != null)
            Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                              data.Name, data.CreationTime, data.ThreadNum);
      }
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
    Public CreationTime As Long
    Public Name As Integer
    Public ThreadNum As Integer
End Class

Module Example
    Public Sub Main()
        Dim taskArray(9) As Task
        For i As Integer = 0 To taskArray.Length - 1
            taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                     Dim data As CustomData = TryCast(obj, CustomData)
                                                     If data Is Nothing Then Return

                                                     data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                                 End Sub,
                                                 New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks})
        Next
        Task.WaitAll(taskArray)

        For Each task In taskArray
            Dim data = TryCast(task.AsyncState, CustomData)
            If data IsNot Nothing Then
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum)
            End If
        Next
    End Sub
End Module
' The example displays output like the following:
'    Task #0 created at 635116451245250515, ran on thread #3, RanToCompletion
'    Task #1 created at 635116451245270515, ran on thread #4, RanToCompletion
'    Task #2 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #3 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #4 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #5 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #6 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #7 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #8 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #9 created at 635116451245270515, ran on thread #3, RanToCompletion

Задачи Task и Task<TResult> предоставляют статическое свойство Factory, возвращающее экземпляр по умолчанию объекта TaskFactory, чтобы можно было вызвать метод как Task.Factory.StartNew(). Кроме того, поскольку в следующем примере задачи относятся к типу System.Threading.Tasks.Task<TResult>, каждая из них имеет открытое свойство Task<TResult>.Result, содержащее результат вычисления. Задачи выполняются асинхронно и могут завершиться в любом порядке. При обращении к свойству Result до завершения вычисления оно блокирует вызывающий поток до тех пор, пока значение не станет доступно.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
        Task<Double>[] taskArray = { Task<Double>.Factory.StartNew(() => DoComputation(1.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(100.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(1000.0)) };

        var results = new Double[taskArray.Length];
        Double sum = 0;

        for (int i = 0; i < taskArray.Length; i++) {
            results[i] = taskArray[i].Result;
            Console.Write("{0:N1} {1}", results[i],
                              i == taskArray.Length - 1 ? "= " : "+ ");
            sum += results[i];
        }
        Console.WriteLine("{0:N1}", sum);
   }

   private static Double DoComputation(Double start)
   {
      Double sum = 0;
      for (var value = start; value <= start + 10; value += .1)
         sum += value;

      return sum;
   }
}
// The example displays the following output:
//        606.0 + 10,605.0 + 100,495.0 = 111,706.0
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim taskArray() = {Task(Of Double).Factory.StartNew(Function() DoComputation(1.0)),
                            Task(Of Double).Factory.StartNew(Function() DoComputation(100.0)),
                            Task(Of Double).Factory.StartNew(Function() DoComputation(1000.0))}

        Dim results(taskArray.Length - 1) As Double
        Dim sum As Double

        For i As Integer = 0 To taskArray.Length - 1
            results(i) = taskArray(i).Result
            Console.Write("{0:N1} {1}", results(i),
                              If(i = taskArray.Length - 1, "= ", "+ "))
            sum += results(i)
        Next
        Console.WriteLine("{0:N1}", sum)
    End Sub

    Private Function DoComputation(start As Double) As Double
        Dim sum As Double
        For value As Double = start To start + 10 Step .1
            sum += value
        Next
        Return sum
    End Function
End Module
' The example displays the following output:
'       606.0 + 10,605.0 + 100,495.0 = 111,706.0

Дополнительные сведения см. в разделе Практическое руководство. Возвращение значения из задачи.

При использовании лямбда-выражения для создания делегата имеется доступ ко всем переменным, видимым на этом этапе в исходном коде. Однако в некоторых случаях, особенно в циклах, лямбда-выражение не перехватывает переменную, как можно было бы ожидать. Оно только перехватывает окончательное значение, а не значение, изменяющееся после каждой итерации. В следующем примере показана эта проблема. В нем счетчик цикла передается лямбда-выражению, создающему экземпляр объекта CustomData, и используется в качестве идентификатора объекта. Как видно из выходных данных примера, все объекты CustomData имеют одинаковые идентификаторы.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in the loop
      // counter. This produces an unexpected result.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj) => {
                                                 var data = new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks};
                                                 data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                 Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                              i );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #10 created at 635116418427727841 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427727841 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427737842 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
    Public CreationTime As Long
    Public Name As Integer
    Public ThreadNum As Integer
End Class

Module Example
    Public Sub Main()
        ' Create the task object by using an Action(Of Object) to pass in the loop
        ' counter. This produces an unexpected result.
        Dim taskArray(9) As Task
        For i As Integer = 0 To taskArray.Length - 1
            taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                     Dim data As New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks}
                                                     data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                                     Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                       data.Name, data.CreationTime, data.ThreadNum)
                                                 End Sub,
                                                 i)
        Next
        Task.WaitAll(taskArray)
    End Sub
End Module
' The example displays output like the following:
'       Task #10 created at 635116418427727841 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427737842 on thread #4.
'       Task #10 created at 635116418427727841 on thread #3.
'       Task #10 created at 635116418427747843 on thread #3.
'       Task #10 created at 635116418427747843 on thread #3.
'       Task #10 created at 635116418427737842 on thread #4.

Доступ к значению для каждой итерации можно получить, предоставив объект состояния задаче через ее конструктор. В следующем примере предыдущий пример изменяется путем использования счетчика цикла при создании объекта CustomData, который, в свою очередь, передается лямбда-выражению. Как видно из выходных данных примера, каждый объект CustomData теперь имеет уникальный идентификатор, основанный на значении счетчика цикла в момент создания экземпляра объекта.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in custom data
      // to the Task constructor. This is useful when you need to capture outer variables
      // from within a loop.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null)
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                  Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
    Public CreationTime As Long
    Public Name As Integer
    Public ThreadNum As Integer
End Class

Module Example
    Public Sub Main()
        ' Create the task object by using an Action(Of Object) to pass in custom data
        ' to the Task constructor. This is useful when you need to capture outer variables
        ' from within a loop. 
        Dim taskArray(9) As Task
        For i As Integer = 0 To taskArray.Length - 1
            taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                     Dim data As CustomData = TryCast(obj, CustomData)
                                                     If data Is Nothing Then Return

                                                     data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                                     Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                       data.Name, data.CreationTime, data.ThreadNum)
                                                 End Sub,
                                                 New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks})
        Next
        Task.WaitAll(taskArray)
    End Sub
End Module
' The example displays output like the following:
'       Task #0 created at 635116412924597583 on thread #3.
'       Task #1 created at 635116412924607584 on thread #4.
'       Task #3 created at 635116412924607584 on thread #4.
'       Task #4 created at 635116412924607584 on thread #4.
'       Task #2 created at 635116412924607584 on thread #3.
'       Task #6 created at 635116412924607584 on thread #3.
'       Task #5 created at 635116412924607584 on thread #4.
'       Task #8 created at 635116412924607584 on thread #4.
'       Task #7 created at 635116412924607584 on thread #3.
'       Task #9 created at 635116412924607584 on thread #4.

Это состояние передается в качестве аргумента делегату задачи, и доступ к нему можно получить из объекта задачи с помощью свойства Task.AsyncState. В следующем примере представлен вариант предыдущего примера. В нем используется свойство AsyncState для отображения сведений об объектах CustomData, переданных лямбда-выражению.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null)
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);
      foreach (var task in taskArray) {
         var data = task.AsyncState as CustomData;
         if (data != null)
            Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                              data.Name, data.CreationTime, data.ThreadNum);
      }
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
    Public CreationTime As Long
    Public Name As Integer
    Public ThreadNum As Integer
End Class

Module Example
    Public Sub Main()
        Dim taskArray(9) As Task
        For i As Integer = 0 To taskArray.Length - 1
            taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                     Dim data As CustomData = TryCast(obj, CustomData)
                                                     If data Is Nothing Then Return

                                                     data.ThreadNum = Thread.CurrentThread.ManagedThreadId
                                                 End Sub,
                                                 New CustomData With {.Name = i, .CreationTime = DateTime.Now.Ticks})
        Next
        Task.WaitAll(taskArray)

        For Each task In taskArray
            Dim data = TryCast(task.AsyncState, CustomData)
            If data IsNot Nothing Then
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum)
            End If
        Next
    End Sub
End Module
' The example displays output like the following:
'    Task #0 created at 635116451245250515, ran on thread #3, RanToCompletion
'    Task #1 created at 635116451245270515, ran on thread #4, RanToCompletion
'    Task #2 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #3 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #4 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #5 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #6 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #7 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #8 created at 635116451245270515, ran on thread #3, RanToCompletion
'    Task #9 created at 635116451245270515, ran on thread #3, RanToCompletion

Идентификатор задачи

Каждая задача получает целочисленный идентификатор, уникально определяющий ее в домене приложения. Доступ к нему можно получить с помощью свойства Task.Id. Этот идентификатор полезен для просмотра сведений о задаче в окнах Параллельные стеки и Задачи отладчика Visual Studio. Он создается только после того, как запрашивается. Поэтому при каждом запуске программы задача может иметь разные идентификаторы. Дополнительные сведения о том, как просматривать идентификаторы задач в отладчике, см. в разделах Использование окна задач и Использование окна "Параллельные стеки".

Параметры создания задачи

Большинство интерфейсов API, в которых создаются задачи, предоставляют перегрузки, принимающие параметр TaskCreationOptions. Указывая один из этих параметров или несколько, пользователь сообщает планировщику задач способ планирования задачи в пуле потоков. Параметры можно объединить с использованием побитовой операции ИЛИ.

В приведенном ниже примере показана задача с параметрами LongRunning и PreferFairness.

var task3 = new Task(() => MyLongRunningMethod(),
                    TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();

Dim task3 = New Task(Sub() MyLongRunningMethod(),
                        TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
task3.Start()

Задачи, потоки и язык и региональные параметры

Каждый поток имеет связанный язык и региональные параметры, а также язык и региональные параметры пользовательского интерфейса, которые определяются свойствами Thread.CurrentCulture и Thread.CurrentUICulture соответственно. Язык и региональные параметры потока используются в таких операциях, как форматирование, анализ, сортировка и сравнение строк. Язык и региональные параметры пользовательского интерфейса потока используются при поиске ресурсов.

Если язык и региональные параметры по умолчанию не заданы для всех потоков в домене приложения с помощью свойств CultureInfo.DefaultThreadCurrentCulture и CultureInfo.DefaultThreadCurrentUICulture, то по умолчанию язык и региональные параметры пользовательского интерфейса потока определяется языком и региональными параметрами системы. Если вы явно задаете язык и региональные параметры потока и запускаете новый поток, этот новый поток не наследует язык и региональные параметры вызывающего потока; вместо них по умолчанию используется язык и региональные параметры системы. Однако при программировании на основе задач задачи используют язык и региональные параметры вызывающего потока, даже если задача выполняется асинхронно в другом потоке.

Следующий пример иллюстрирует это. Он изменяет текущий язык и региональные параметры приложения на французский (Франция), а если французский (Франция) уже является текущим языком, то изменяет его на английский (США). Затем он вызывает делегат с именем formatDelegate, который возвращает некоторые числа в виде значений валюты в новом языке и региональных параметрах. Независимо от того, вызывается ли делегат задачей синхронно или асинхронно, задача использует язык и региональные параметры вызывающего потока.

using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
       decimal[] values = { 163025412.32m, 18905365.59m };
       string formatString = "C2";
       Func<String> formatDelegate = () => { string output = String.Format("Formatting using the {0} culture on thread {1}.\n",
                                                                           CultureInfo.CurrentCulture.Name,
                                                                           Thread.CurrentThread.ManagedThreadId);
                                             foreach (var value in values)
                                                output += String.Format("{0}   ", value.ToString(formatString));

                                             output += Environment.NewLine;
                                             return output;
                                           };

       Console.WriteLine("The example is running on thread {0}",
                         Thread.CurrentThread.ManagedThreadId);
       // Make the current culture different from the system culture.
       Console.WriteLine("The current culture is {0}",
                         CultureInfo.CurrentCulture.Name);
       if (CultureInfo.CurrentCulture.Name == "fr-FR")
          Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
       else
          Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");

       Console.WriteLine("Changed the current culture to {0}.\n",
                         CultureInfo.CurrentCulture.Name);

       // Execute the delegate synchronously.
       Console.WriteLine("Executing the delegate synchronously:");
       Console.WriteLine(formatDelegate());

       // Call an async delegate to format the values using one format string.
       Console.WriteLine("Executing a task asynchronously:");
       var t1 = Task.Run(formatDelegate);
       Console.WriteLine(t1.Result);

       Console.WriteLine("Executing a task synchronously:");
       var t2 = new Task<String>(formatDelegate);
       t2.RunSynchronously();
       Console.WriteLine(t2.Result);
   }
}
// The example displays the following output:
//         The example is running on thread 1
//         The current culture is en-US
//         Changed the current culture to fr-FR.
//
//         Executing the delegate synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task asynchronously:
//         Formatting using the fr-FR culture on thread 3.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
Imports System.Globalization
Imports System.Threading

Module Example
    Public Sub Main()
        Dim values() As Decimal = {163025412.32D, 18905365.59D}
        Dim formatString As String = "C2"
        Dim formatDelegate As Func(Of String) = Function()
                                                    Dim output As String = String.Format("Formatting using the {0} culture on thread {1}.",
                                                                                         CultureInfo.CurrentCulture.Name,
                                                                                         Thread.CurrentThread.ManagedThreadId)
                                                    output += Environment.NewLine
                                                    For Each value In values
                                                        output += String.Format("{0}   ", value.ToString(formatString))
                                                    Next
                                                    output += Environment.NewLine
                                                    Return output
                                                End Function

        Console.WriteLine("The example is running on thread {0}",
                          Thread.CurrentThread.ManagedThreadId)
        ' Make the current culture different from the system culture.
        Console.WriteLine("The current culture is {0}",
                          CultureInfo.CurrentCulture.Name)
        If CultureInfo.CurrentCulture.Name = "fr-FR" Then
            Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Else
            Thread.CurrentThread.CurrentCulture = New CultureInfo("fr-FR")
        End If
        Console.WriteLine("Changed the current culture to {0}.",
                          CultureInfo.CurrentCulture.Name)
        Console.WriteLine()

        ' Execute the delegate synchronously.
        Console.WriteLine("Executing the delegate synchronously:")
        Console.WriteLine(formatDelegate())

        ' Call an async delegate to format the values using one format string.
        Console.WriteLine("Executing a task asynchronously:")
        Dim t1 = Task.Run(formatDelegate)
        Console.WriteLine(t1.Result)

        Console.WriteLine("Executing a task synchronously:")
        Dim t2 = New Task(Of String)(formatDelegate)
        t2.RunSynchronously()
        Console.WriteLine(t2.Result)
    End Sub
End Module

' The example displays the following output:
'
'          The example is running on thread 1
'          The current culture is en-US
'          Changed the current culture to fr-FR.
'
'          Executing the delegate synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task asynchronously:
'          Formatting Imports the fr-FR culture on thread 3.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €

Примечание

В версиях .NET Framework до версии 4.6 язык и региональные параметры задачи определяются языком и региональными параметрами потока, в котором она выполняется, а не языком и региональными параметрами вызывающего потока. Для асинхронных задач это означает, что язык и региональные параметры, используемые задачей, могут отличаться от языка и региональных параметров вызывающего потока.

Дополнительные сведения об асинхронных задачах и языке и региональных параметрах см. в разделе "Язык и региональные параметры и асинхронные операции на основе задач" в описании класса CultureInfo.

Создание продолжений задач

С помощью методов Task.ContinueWith и Task<TResult>.ContinueWith можно указать задачу, которую нужно запускать по завершении предшествующей задачи. Делегат задачи продолжения передается в качестве ссылки на предшествующую задачу, чтобы он мог проверить состояние предшествующей задачи и, получив значение свойства Task<TResult>.Result, использовать выходные данные предшествующей задачи в качестве входных данных для продолжения.

В следующем примере задача getData запускается вызовом метода TaskFactory.StartNew<TResult>(Func<TResult>). Задача processData запускается автоматически по завершении задачи getData, а задача displayData запускается по завершении задачи processData. Задача getData создает целочисленный массив, доступный задаче processData через свойство getData задачи Task<TResult>.Result. Задача processData обрабатывает этот массив и возвращает результат, тип которого определяется на основе типа возвращаемого значения лямбда-выражения, переданного методу Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>). Задача displayData выполняется автоматически по завершении задачи processData, и объект Tuple<T1,T2,T3>, возвращенный лямбда-выражением processData, доступен задаче displayData через свойство processData задачи Task<TResult>.Result. Задача displayData принимает результат задачи processData и выдает результат, тип которого определяется аналогичным образом. Этот результат становится доступным программе в свойстве Result.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var getData = Task.Factory.StartNew(() => {
                                             Random rnd = new Random();
                                             int[] values = new int[100];
                                             for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                values[ctr] = rnd.Next();

                                             return values;
                                          } );
      var processData = getData.ContinueWith((x) => {
                                                int n = x.Result.Length;
                                                long sum = 0;
                                                double mean;

                                                for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                                   sum += x.Result[ctr];

                                                mean = sum / (double) n;
                                                return Tuple.Create(n, sum, mean);
                                             } );
      var displayData = processData.ContinueWith((x) => {
                                                    return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                         x.Result.Item1, x.Result.Item2,
                                                                         x.Result.Item3);
                                                 } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim getData = Task.Factory.StartNew(Function()
                                                Dim rnd As New Random()
                                                Dim values(99) As Integer
                                                For ctr = 0 To values.GetUpperBound(0)
                                                    values(ctr) = rnd.Next()
                                                Next
                                                Return values
                                            End Function)
        Dim processData = getData.ContinueWith(Function(x)
                                                   Dim n As Integer = x.Result.Length
                                                   Dim sum As Long
                                                   Dim mean As Double

                                                   For ctr = 0 To x.Result.GetUpperBound(0)
                                                       sum += x.Result(ctr)
                                                   Next
                                                   mean = sum / n
                                                   Return Tuple.Create(n, sum, mean)
                                               End Function)
        Dim displayData = processData.ContinueWith(Function(x)
                                                       Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                            x.Result.Item1, x.Result.Item2,
                                                                            x.Result.Item3)
                                                   End Function)
        Console.WriteLine(displayData.Result)
    End Sub
End Module
' The example displays output like the following:
'   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Поскольку метод Task.ContinueWith является экземплярным, вызовы этого метода можно объединять в цепочку, а не создавать экземпляр объекта Task<TResult> для каждой предшествующей задачи. Следующий пример функционально идентичен предыдущему, за исключением того, вызовы метода Task.ContinueWith в нем объединяются в цепочку. Обратите внимание, что объект Task<TResult>, возвращаемый цепочкой вызовов метода, является последней задачей продолжения.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var displayData = Task.Factory.StartNew(() => {
                                                 Random rnd = new Random();
                                                 int[] values = new int[100];
                                                 for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                    values[ctr] = rnd.Next();

                                                 return values;
                                              } ).
                        ContinueWith((x) => {
                                        int n = x.Result.Length;
                                        long sum = 0;
                                        double mean;

                                        for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                           sum += x.Result[ctr];

                                        mean = sum / (double) n;
                                        return Tuple.Create(n, sum, mean);
                                     } ).
                        ContinueWith((x) => {
                                        return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                             x.Result.Item1, x.Result.Item2,
                                                             x.Result.Item3);
                                     } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim displayData = Task.Factory.StartNew(Function()
                                                    Dim rnd As New Random()
                                                    Dim values(99) As Integer
                                                    For ctr = 0 To values.GetUpperBound(0)
                                                        values(ctr) = rnd.Next()
                                                    Next
                                                    Return values
                                                End Function). _
                          ContinueWith(Function(x)
                                           Dim n As Integer = x.Result.Length
                                           Dim sum As Long
                                           Dim mean As Double

                                           For ctr = 0 To x.Result.GetUpperBound(0)
                                               sum += x.Result(ctr)
                                           Next
                                           mean = sum / n
                                           Return Tuple.Create(n, sum, mean)
                                       End Function). _
                          ContinueWith(Function(x)
                                           Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                              x.Result.Item1, x.Result.Item2,
                                                              x.Result.Item3)
                                       End Function)
        Console.WriteLine(displayData.Result)
    End Sub
End Module
' The example displays output like the following:
'   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

С помощью методов ContinueWhenAll и ContinueWhenAny можно продолжить выполнение с нескольких задач.

Подробнее см. в разделе Создание цепочки задач с помощью задач продолжения.

Создание отсоединенных дочерних задач

Если в пользовательском коде, выполняемом в некоторой задаче, создается новая задача и не задается параметр AttachedToParent, новая задача не синхронизируется с родительской никаким особым способом. Такой тип несинхронизированной задачи называется отсоединенной вложенной задачей или отсоединенной дочерней задачей. В следующем примере показана задача, создающая одну отсоединенную дочернюю задачу.

var outer = Task.Factory.StartNew(() =>
{
    Console.WriteLine("Outer task beginning.");

    var child = Task.Factory.StartNew(() =>
    {
        Thread.SpinWait(5000000);
        Console.WriteLine("Detached task completed.");
    });
});

outer.Wait();
Console.WriteLine("Outer task completed.");
// The example displays the following output:
//    Outer task beginning.
//    Outer task completed.
//    Detached task completed.
Dim outer = Task.Factory.StartNew(Sub()
                                      Console.WriteLine("Outer task beginning.")
                                      Dim child = Task.Factory.StartNew(Sub()
                                                                            Thread.SpinWait(5000000)
                                                                            Console.WriteLine("Detached task completed.")
                                                                        End Sub)
                                  End Sub)
outer.Wait()
Console.WriteLine("Outer task completed.")
' The example displays the following output:
'     Outer task beginning.
'     Outer task completed.
'    Detached child completed.

Обратите внимание, что родительская задача не ожидает завершения отсоединенной дочерней задачи.

Создание дочерних задач

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

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
                      Console.WriteLine("Parent task beginning.");
                      for (int ctr = 0; ctr < 10; ctr++) {
                         int taskNo = ctr;
                         Task.Factory.StartNew((x) => {
                                                  Thread.SpinWait(5000000);
                                                  Console.WriteLine("Attached child #{0} completed.",
                                                                    x);
                                               },
                                               taskNo, TaskCreationOptions.AttachedToParent);
                      }
                   });

      parent.Wait();
      Console.WriteLine("Parent task completed.");
   }
}
// The example displays output like the following:
//       Parent task beginning.
//       Attached child #9 completed.
//       Attached child #0 completed.
//       Attached child #8 completed.
//       Attached child #1 completed.
//       Attached child #7 completed.
//       Attached child #2 completed.
//       Attached child #6 completed.
//       Attached child #3 completed.
//       Attached child #5 completed.
//       Attached child #4 completed.
//       Parent task completed.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim parent = Task.Factory.StartNew(Sub()
                                               Console.WriteLine("Parent task beginning.")
                                               For ctr As Integer = 0 To 9
                                                   Dim taskNo As Integer = ctr
                                                   Task.Factory.StartNew(Sub(x)
                                                                             Thread.SpinWait(5000000)
                                                                             Console.WriteLine("Attached child #{0} completed.",
                                                                                               x)
                                                                         End Sub,
                                                                         taskNo, TaskCreationOptions.AttachedToParent)
                                               Next
                                           End Sub)
        parent.Wait()
        Console.WriteLine("Parent task completed.")
    End Sub
End Module
' The example displays output like the following:
'       Parent task beginning.
'       Attached child #9 completed.
'       Attached child #0 completed.
'       Attached child #8 completed.
'       Attached child #1 completed.
'       Attached child #7 completed.
'       Attached child #2 completed.
'       Attached child #6 completed.
'       Attached child #3 completed.
'       Attached child #5 completed.
'       Attached child #4 completed.
'       Parent task completed.

В родительской задаче может использоваться параметр TaskCreationOptions.DenyChildAttach, не позволяющий другим задачам присоединяться к ней. Дополнительные сведения см. в разделе Присоединенные и отсоединенные дочерние задачи.

Ожидание завершения задач

Типы System.Threading.Tasks.Task и System.Threading.Tasks.Task<TResult> предоставляют несколько перегрузок методов Task.Wait, которые позволяют ожидать завершения задачи. Кроме того, перегрузки статических методов Task.WaitAll и Task.WaitAny позволяют ожидать завершения какого-либо или всех массивов задач.

Как правило, ожидание задачи выполняется по одной из следующих причин.

  • Основной поток зависит от конечного результата, вычисленного задачей.

  • Необходимо обрабатывать исключения, которые могут быть созданы из задачи.

  • Приложение может завершиться до окончания выполнения всех задач. Например, выполнение консольных приложений завершается после выполнения всего синхронного кода в Main (точке входа приложения).

В следующем примере показан базовый шаблон, в котором не указана обработка исключений.

Task[] tasks = new Task[3]
{
    Task.Factory.StartNew(() => MethodA()),
    Task.Factory.StartNew(() => MethodB()),
    Task.Factory.StartNew(() => MethodC())
};

//Block until all tasks complete.
Task.WaitAll(tasks);

// Continue on this thread...
Dim tasks() =
{
    Task.Factory.StartNew(Sub() MethodA()),
    Task.Factory.StartNew(Sub() MethodB()),
    Task.Factory.StartNew(Sub() MethodC())
}

' Block until all tasks complete.
Task.WaitAll(tasks)

' Continue on this thread...

Пример обработки исключений см. в разделе Обработка исключений.

Некоторые перегрузки позволяют задать время ожидания, а другие принимают дополнительный объект CancellationToken в качестве входного параметра, чтобы ожидание можно было отменить либо программно, либо в ответ на введенные пользователем данные.

При ожидании задачи неявно ожидаются все ее дочерние задачи, созданные с помощью параметра TaskCreationOptions.AttachedToParent. Task.Wait выполняет возврат немедленно, если задача уже завершена. Любые исключения, вызванные задачей, будут созданы методом Task.Wait, даже если метод Task.Wait был вызван после завершения задачи.

Составление задач

Классы Task и Task<TResult> предоставляют несколько методов, позволяющих создать ряд задач для реализации общих шаблонов и более эффективного использования асинхронных возможностей языка, предусмотренных в языках C#, Visual Basic и F#. В этом подразделе описаны методы WhenAll, WhenAny, Delay и FromResult.

Task.WhenAll

Метод Task.WhenAll асинхронно ожидает завершения выполнения нескольких объектов Task или Task<TResult>. Он предоставляет перегруженные версии, позволяющие ожидать неединобразные наборы задач. Например, можно ожидать завершения выполнения нескольких объектов Task и Task<TResult> от одного вызова метода.

Task.WhenAny

Метод Task.WhenAny асинхронно ожидает завершения выполнения одного из нескольких объектов Task или Task<TResult>. Как и метод Task.WhenAll, этот метод предоставляет перегруженные версии, позволяющие ожидать неединобразные наборы задач. Метод WhenAny особенно полезен в следующих ситуациях.

  • Избыточные операции. Рассмотрим алгоритм или операцию, которые можно выполнить несколькими способами. Метод WhenAny можно использовать для выбора операции, завершающейся первой, и последующей отмены оставшихся операций.

  • Операции с чередованием. Можно запустить несколько операций, которые все должны завершиться, и использовать метод WhenAny для обработки результатов при завершении каждой операции. После завершения одной операции можно запустить одну или несколько дополнительных задач.

  • Регулируемые операции. Метод WhenAny можно использовать для расширения предыдущего сценария путем ограничения количества одновременно выполняемых операций.

  • Операции с истекшим сроком действия. Метод WhenAny можно использовать, чтобы сделать выбор между одной или несколькими задачами и задачей, завершающейся после определенного времени, например задачей, возвращаемой методом Delay. Метод Delay описан в следующем разделе.

Task.Delay

Метод Task.Delay создает объект Task, завершающийся после определенного времени. Этот метод можно использовать для создания циклов, которые иногда запрашивают данные, вводят тайм-ауты, задерживают обработку вводимых пользователем данных на заранее определенное время и т. д.

Task(T).FromResult

С помощью метода Task.FromResult можно создать объект Task<TResult>, содержащий предварительно вычисленный результат. Этот метод полезен тогда, когда выполняется асинхронная операция, возвращающая объект Task<TResult>, и результат этого объекта Task<TResult> уже вычислен. Пример использования метода FromResult для получения результатов асинхронных операций загрузки, удерживаемых в кэше, см. в разделе Практическое руководство. Создание предварительно вычисляемых задач.

Обработка исключений в задачах

Если задача создает одно или несколько исключений, они заключаются в исключение AggregateException. Это исключение распространяется обратно в поток, который соединяется с задачей и обычно является потоком, ожидающим завершения задачи или обращающимся к свойству Result. Такое поведение служит для принудительного выполнения политики .NET Framework, согласно которой все необработанные исключения по умолчанию должны завершать процесс. Ниже указаны элементы блока try/catch, с помощью любого из которых вызывающий код может обрабатывать исключения:

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

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

Отмена задач

Класс Task поддерживает совместную отмену и полностью интегрирован с классами System.Threading.CancellationTokenSource и System.Threading.CancellationToken, появившимися в .NET Framework 4. Большинство конструкторов в классе System.Threading.Tasks.Task принимают объект CancellationToken в качестве входного параметра. Многие из перегрузок StartNew и Run также содержат параметр CancellationToken.

Можно создать токен и выдать запрос отмены позднее с помощью класса CancellationTokenSource. Передайте токен Task в качестве аргумента и ссылайтесь на тот же токен в пользовательском делегате, который не отвечает на запрос отмены.

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

Класс TaskFactory

Класс TaskFactory предоставляет статические методы, которые инкапсулируют некоторые распространенные шаблоны для создания и запуска задач и задач продолжения.

Класс TaskFactory доступен как статическое свойство класса Task или Task<TResult>. Кроме того, класс TaskFactory можно создать напрямую и указать различные параметры, включающие CancellationToken, параметр TaskCreationOptions, параметр TaskContinuationOptions или TaskScheduler. Любые параметры, задаваемые при создании фабрики задач, будут применяться ко всем созданным задачам, если задача Task не создана с помощью перечисления TaskCreationOptions. В этом случае параметры задачи переопределяют параметры фабрики задач.

Задачи без делегатов

В некоторых случаях может потребоваться использовать Task для инкапсуляции некоторой асинхронной операции, которая выполняется внешним компонентом, а не собственным пользовательским делегатом. Если операция основана на шаблоне Begin/End модели асинхронного программирования, можно использовать методы FromAsync. В противном случае можно использовать объект TaskCompletionSource<TResult> для заключения операции в задачу, чтобы получить некоторые преимущества программирования Task, например поддержку распространения исключений и продолжений. Для получения дополнительной информации см. TaskCompletionSource<TResult>.

Пользовательские планировщики

Большинство разработчиков приложений или библиотек не обращают внимания на то, на каком процессоре запускается задача, как она синхронизирует свою работу с другими задачами или как она планируется в System.Threading.ThreadPool. Им только требуется, чтобы она выполнялась максимально эффективно на главном компьютере. Если требуется более точное управление сведениями планирования, библиотека параллельных задач позволяет настроить некоторые параметры в планировщике задач по умолчанию и даже предоставить пользовательский планировщик. Для получения дополнительной информации см. TaskScheduler.

Библиотека параллельных задач имеет несколько новых открытых типов, которые полезны в параллельных и последовательных сценариях. Они включают несколько потокобезопасных, быстрых и масштабируемых классов коллекций в пространстве имен System.Collections.Concurrent и несколько новых типов синхронизации, например System.Threading.Semaphore и System.Threading.ManualResetEventSlim, которые более эффективны, чем их предшественники для определенных типов рабочих нагрузок. Другие новые типы в .NET Framework 4, например System.Threading.Barrier и System.Threading.SpinLock, предоставляют функциональные возможности, которые не были доступны в более ранних выпусках. Дополнительные сведения см. в разделе Структуры данных для параллельного программирования.

Настраиваемые типы задач

Наследование из System.Threading.Tasks.Task или System.Threading.Tasks.Task<TResult> не рекомендуется. Вместо этого рекомендуется с помощью свойства AsyncState связать дополнительные данные или состояние с объектом Task или Task<TResult>. Можно также использовать методы расширения для расширения функциональных возможностей классов Task и Task<TResult>. Дополнительные сведения о методах расширения см. в разделах Методы расширения и Методы расширения.

Если необходимо наследовать от Task или Task<TResult>, классы Run, System.Threading.Tasks.TaskFactory, System.Threading.Tasks.TaskFactory<TResult> и System.Threading.Tasks.TaskCompletionSource<TResult> нельзя использовать для создания экземпляров настраиваемого типа задач, так как эти механизмы создают только объекты Task и Task<TResult>. Кроме того, механизмы продолжения задачи, работу которых обеспечивают Task, Task<TResult>, TaskFactory и TaskFactory<TResult>, нельзя использовать для создания экземпляров настраиваемого типа задач, поскольку эти механизмы также создают только объекты Task и Task<TResult>.

Заголовок Описание
Создание цепочки задач с помощью задач продолжения Описание работы продолжений.
Присоединенные и отсоединенные дочерние задачи Описание различий между присоединенными и отсоединенными дочерними задачами.
Отмена задач Описание поддержки отмены, встроенной в объект Task.
Обработка исключений Описание обработки исключений в параллельных потоках.
Практическое руководство. Использование функции Parallel.Invoke для выполнения параллельных операций Описание использования Invoke.
Практическое руководство. Возвращение значения из задачи Описание возврата значений из задач.
Практическое руководство. Отмена задачи и ее дочерних элементов Описание отмены задач.
Практическое руководство. Создание предварительно вычисляемых задач Описание использования метода Task.FromResult для получения результатов асинхронных операций загрузки, удерживаемых в кэше.
Практическое руководство. Переход по двоичному дереву с помощью параллельных задач Описание использования задач для прохождения двоичного дерева.
Практическое руководство. Извлечение вложенной задачи из оболочки Демонстрация использования метода расширения Unwrap.
Параллелизм данных Описывает способы использования методов For и ForEach для создания параллельных циклов для данных.
Параллельное программирование Узел верхнего уровня для параллельного программирования в .NET Framework.

См. также