Асинхронное программирование на основе задач
Библиотека параллельных задач (TPL) основана на концепции задачи, представляющей асинхронную операцию. В некотором смысле задача напоминает поток или ThreadPool рабочий элемент, но на более высоком уровне абстракции. Термин параллелизм задач относится к одной или нескольким независимым задачам, выполняемым одновременно. Задачи предоставляют два основных преимущества:
Более эффективное и более масштабируемое использование системных ресурсов.
В фоновом режиме задачи ставятся в очередь на ThreadPool, который был усовершенствован с помощью алгоритмов, определяющих и корректирующих количество потоков. Эти алгоритмы обеспечивают балансировку нагрузки для максимальной пропускной способности. Этот процесс делает задачи относительно упрощенными, и вы можете создать многие из них, чтобы включить детализированный параллелизм.
Более высокий уровень управления программой, чем это возможно с помощью потока или рабочего элемента.
Задачи и фреймворк, построенные вокруг них, предоставляют широкий набор API, поддерживающих ожидание, отмену, продолжения, надежную обработку исключений, детализированный статус, пользовательское планирование и многое другое.
По обеим причинам TPL является предпочтительным API для написания многопоточных, асинхронных и параллельных кодов в .NET.
Создание и выполнение задач в фоновом режиме
Метод Parallel.Invoke предоставляет удобный способ одновременного выполнения любого количества произвольных инструкций. Просто передайте делегат Action для выполнения каждой задачи. Самый простой способ создания этих делегатов — использовать лямбда-выражения. Лямбда-выражение может вызывать именованный метод или предоставлять встроенный код. В следующем примере показан базовый вызов Invoke, который создает и запускает две задачи, которые выполняются одновременно. Первая задача представлена лямбда-выражением, которое вызывает метод с именем DoSomeWork
, а вторая задача представлена лямбда-выражением, которое вызывает метод с именем DoSomeOtherWork
.
Заметка
В этой документации используются лямбда-выражения для определения делегатов в TPL. Если вы не знакомы с лямбда-выражениями в C# или Visual Basic, см. лямбда-выражения в PLINQ и TPL.
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 позволяют указать задачу, которая будет запущена после завершения предшествующей задачи . Делегату задачи продолжения передается ссылка на предшествующую задачу, чтобы он мог проанализировать состояние этой задачи. Получив значение свойства Task<TResult>.Result, вы сможете использовать выходные данные предшествующего элемента в качестве входных данных для продолжения.
В следующем примере задача getData
запускается вызовом метода TaskFactory.StartNew<TResult>(Func<TResult>). Задача processData
запускается автоматически при завершении getData
и displayData
запускается при завершении processData
.
getData
создает целочисленный массив, который доступен задаче processData
через свойство Task<TResult>.Result задачи getData
. Задача processData
обрабатывает этот массив и возвращает результат, тип которого выводится из возвращаемого типа лямбда-выражения, переданного методу Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>). Задача displayData
выполняется автоматически при завершении processData
, а объект Tuple<T1,T2,T3>, возвращаемый лямбда-выражением processData
, доступен для задачи displayData
через свойство Task<TResult>.Result задачи processData
. Задача 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. Дополнительные сведения см. в разделе TPL и традиционной модели асинхронного программирования .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.Barrier и System.Threading.SpinLock, предоставляют функциональные возможности, недоступные в предыдущих выпусках. Дополнительные сведения см. в Структуры данных для параллельного программирования.
Пользовательские типы задач
Мы рекомендуем не наследовать от System.Threading.Tasks.Task или System.Threading.Tasks.Task<TResult>. Вместо этого рекомендуется использовать свойство AsyncState для связывания дополнительных данных или состояний с объектом Task или Task<TResult>. Можно также использовать методы расширения для расширения функциональных возможностей классов Task и Task<TResult>. Дополнительную информацию о методах расширения см. в списках Методы расширения и Методы расширения.
Если необходимо наследовать от Task или Task<TResult>, нельзя использовать Run и классы System.Threading.Tasks.TaskFactory, System.Threading.Tasks.TaskFactory<TResult>или System.Threading.Tasks.TaskCompletionSource<TResult> для создания экземпляров пользовательского типа задачи. Их нельзя использовать, так как эти классы создают только объекты Task и Task<TResult>. Кроме того, нельзя использовать механизмы продолжения задач, предоставляемые Task, Task<TResult>, TaskFactoryи TaskFactory<TResult> для создания экземпляров пользовательского типа задачи. Их нельзя использовать, так как эти классы также создают только Task и Task<TResult> объектов.
Связанные разделы
Титул | Описание |
---|---|
Построение цепочек задач с помощью задач продолжения | Описывает, как работают продолжения. |
присоединенные и отсоединяемые дочерние задачи | Описывает разницу между присоединенными и отсоединенными дочерними задачами. |
отмена задачи | Описывает поддержку отмены, встроенную в объект Task. |
обработка исключений | Описывает, как обрабатываются исключения в параллельных потоках. |
практическое руководство. Использование Parallel.Invoke для выполнения параллельных операций | Описывает использование Invoke. |
Как вернуть значение из Task | Описывает, как возвращать значения из задач. |
Как отменить задачу и её дочерние задачи | Описывает, как отменить задачи. |
Практическое руководство. Создание предварительно вычисляемых задач | Описывает использование метода Task.FromResult для получения результатов асинхронных операций загрузки, которые хранятся в кэше. |
Как: Обход двоичного дерева с использованием параллельных задач | Описывает, как использовать задачи для обхода двоичного дерева. |
Как: Развернуть вложенную задачу | Демонстрирует использование метода расширения Unwrap. |
Параллельная обработка данных | Описывает использование For и ForEach для создания параллельных циклов по данным. |
параллельное программирование | Узел верхнего уровня для параллельного программирования .NET Framework. |