Асинхронное программирование на основе задач
Библиотека параллельных задач (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 like the following:
// Hello from thread 'Main'.
// Hello from taskA.
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 like the following:
// Hello from thread 'Main'.
// Hello from taskA.
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 .
В следующем примере показана задача с параметрами и PreferFairness параметрамиLongRunning.
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 можно указать задачу, которую нужно запускать по завершении предшествующей задачи. Делегат задачи продолжения передает ссылку на предшествующую задачу, чтобы она училась изучить состояние предшествующей задачи. И, извлекая значение 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 предоставляют 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 предоставляет статические методы, которые инкапсулируют общие шаблоны для создания и запуска задач и задач продолжения.
Наиболее распространенным шаблоном является StartNew, который создает и запускает задачу в одном операторе.
Для создания задач продолжения из нескольких предшествующих задач используйте метод ContinueWhenAll, метод ContinueWhenAny или их аналоги из класса Task<TResult>. Подробнее см. в разделе Создание цепочки задач с помощью задач продолжения.
Чтобы инкапсулировать методы
BeginX
иEndX
модели асинхронного программирования в экземпляре Task или Task<TResult>, используйте методы FromAsync. Дополнительные сведения см. в разделе Библиотека параллельных задач и традиционное асинхронное программирование .NET Framework.
Класс 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.SpinLockфункциональные возможности, System.Threading.Barrier недоступные в предыдущих выпусках. Дополнительные сведения см. в разделе Структуры данных для параллельного программирования.
Настраиваемые типы задач
Рекомендуется не наследовать от 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> объекты.
Связанные разделы
Заголовок | Описание |
---|---|
Создание цепочки задач с помощью задач продолжения | Описание работы продолжений. |
Присоединенные и отсоединенные дочерние задачи | Описание различий между присоединенными и отсоединенными дочерними задачами. |
Отмена задач | Описывает поддержку отмены, встроенную Task в объект. |
Обработка исключений | Описание обработки исключений в параллельных потоках. |
Практическое руководство. Использование функции Parallel.Invoke для выполнения параллельных операций | Описание использования Invoke. |
Практическое руководство. Возвращение значения из задачи | Описание возврата значений из задач. |
Практическое руководство. Отмена задачи и ее дочерних элементов | Описание отмены задач. |
Практическое руководство. Создание предварительно вычисляемых задач | Описание использования метода Task.FromResult для получения результатов асинхронных операций загрузки, удерживаемых в кэше. |
Практическое руководство. Переход по двоичному дереву с помощью параллельных задач | Описание использования задач для прохождения двоичного дерева. |
Практическое руководство. Извлечение вложенной задачи из оболочки | Демонстрация использования метода расширения Unwrap. |
Параллелизм данных | Описывает способы использования методов For и ForEach для создания параллельных циклов для данных. |
Параллельное программирование | Узел верхнего уровня для платформа .NET Framework параллельного программирования. |