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

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

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

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

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

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

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

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

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

Примечание.

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

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

Примечание.

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

Дополнительные сведения см. в разделе Практическое руководство. Использование метода 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 Lambda
{
   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 as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.
Imports System.Threading

Namespace Lambda
    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.
End Namespace

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

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

namespace Run;

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 as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.
Imports System.Threading

Namespace Run
    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.
End Namespace

Для создания и запуска задачи в одной операции можно также использовать метод TaskFactory.StartNew. Как показано в следующем примере, этот метод можно использовать в следующих случаях:

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

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

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

namespace TaskIntro;

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

public class AsyncState
{
    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, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading

Namespace AsyncState
    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 = Environment.CurrentManagedThreadId
                                                     End Sub,
                New CustomData With {.Name = i, .CreationTime = Date.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 635116412924597583, ran on thread #3.
    '     Task #1 created at 635116412924607584, ran on thread #4.
    '     Task #2 created at 635116412924607584, ran on thread #4.
    '     Task #3 created at 635116412924607584, ran on thread #4.
    '     Task #4 created at 635116412924607584, ran on thread #3.
    '     Task #5 created at 635116412924607584, ran on thread #3.
    '     Task #6 created at 635116412924607584, ran on thread #4.
    '     Task #7 created at 635116412924607584, ran on thread #4.
    '     Task #8 created at 635116412924607584, ran on thread #3.
    '     Task #9 created at 635116412924607584, ran on thread #4.
End Namespace

Задачи 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 Result
{
   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

Namespace Result
    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
End Namespace

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

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

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

namespace Example.Iterations;

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

public class IterationTwo
{
   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

Namespace IterationsTwo
    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 = Date.Now.Ticks}
                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                         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.
End Namespace

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

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

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

public class IterationOne
{
   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

Namespace IterationsOne
    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 = Environment.CurrentManagedThreadId
                                                         Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                         data.Name, data.CreationTime, data.ThreadNum)
                                                     End Sub,
                    New CustomData With {.Name = i, .CreationTime = Date.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.
End Namespace

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

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

namespace TaskIntro;

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

public class AsyncState
{
    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, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading

Namespace AsyncState
    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 = Environment.CurrentManagedThreadId
                                                     End Sub,
                New CustomData With {.Name = i, .CreationTime = Date.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 635116412924597583, ran on thread #3.
    '     Task #1 created at 635116412924607584, ran on thread #4.
    '     Task #2 created at 635116412924607584, ran on thread #4.
    '     Task #3 created at 635116412924607584, ran on thread #4.
    '     Task #4 created at 635116412924607584, ran on thread #3.
    '     Task #5 created at 635116412924607584, ran on thread #3.
    '     Task #6 created at 635116412924607584, ran on thread #4.
    '     Task #7 created at 635116412924607584, ran on thread #4.
    '     Task #8 created at 635116412924607584, ran on thread #3.
    '     Task #9 created at 635116412924607584, ran on thread #4.
End Namespace

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

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

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

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

В следующем примере показана задача с параметрами 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, предшествующих платформа .NET Framework 4.6, язык и региональные параметры задачи определяются языком и региональными параметрами потока, на котором он выполняется, а не языком и региональными параметрами вызывающего потока. Для асинхронных задач язык и региональные параметры, используемые задачей, могут отличаться от языка и региональных параметров вызывающего потока.

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

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

С помощью методов Task.ContinueWith и Task<TResult>.ContinueWith можно указать задачу, которую нужно запускать по завершении предшествующей задачи. Делегат задачи продолжения передает ссылку на задачу antecedent, чтобы она училась изучить состояние задачи. И, извлекая значение 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 ContinuationOne
{
   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

Namespace ContinuationsOne
    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
End Namespace

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

using System;
using System.Threading.Tasks;

public class ContinuationTwo
{
   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

Namespace ContinuationsTwo
    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
End Namespace

С помощью методов 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 параметр для выражения структурированного параллелизма задач, так как родительская задача неявно ожидает завершения всех вложенных дочерних задач. В следующем примере показана родительская задача, которая создает 10 вложенных дочерних задач. В этом примере метод вызывает Task.Wait ожидание завершения родительской задачи. Не обязательно явно ожидать завершения присоединенных дочерних задач.

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

public class Child
{
   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

Namespace Child
    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.
End Namespace

В родительской задаче может использоваться параметр 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<TResult> Классы Task предоставляют несколько методов для создания нескольких задач. Эти методы реализуют общие шаблоны и лучше используют асинхронные языковые функции, предоставляемые 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. Им только требуется, чтобы она выполнялась максимально эффективно на главном компьютере. Если требуется более подробный контроль над подробными сведениями о планировании, TPL позволяет настроить некоторые параметры планировщика задач по умолчанию и даже предоставить настраиваемый планировщик. Дополнительные сведения см. в разделе TaskScheduler.

В TPL есть несколько новых общедоступных типов, которые полезны в параллельных и последовательных сценариях. К ним относятся несколько потоковобезопасных, быстрых и масштабируемых классов коллекций 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.TaskFactorySystem.Threading.Tasks.TaskFactory<TResult>классы или System.Threading.Tasks.TaskCompletionSource<TResult> классы для создания экземпляров пользовательского типа задачи. Их нельзя использовать, так как эти классы создают только Task объекты и Task<TResult> объекты. Кроме того, нельзя использовать механизмы продолжения задач, предоставляемые Task, Task<TResult>TaskFactoryи TaskFactory<TResult> создавать экземпляры пользовательского типа задачи. Их нельзя использовать, так как эти классы также создают только Task объекты и Task<TResult> объекты.

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

См. также