Programação assíncrona baseada em tarefas
A TPL (Task Parallel Library) baseia-se no conceito de task, que representa uma operação assíncrona. De certa maneira, uma tarefa assemelha-se a uma linha de execução ou um item de trabalho ThreadPool, mas num nível mais elevado de abstração. O termo paralelismo de tarefas refere-se a uma ou mais tarefas independentes executadas simultaneamente. As tarefas oferecem dois benefícios principais:
Utilização mais eficiente e escalável dos recursos do sistema.
Nos bastidores, as tarefas são enfileiradas para o ThreadPool, que foi aprimorado com algoritmos que determinam e se ajustam ao número de threads disponíveis. Esses algoritmos fornecem balanceamento de carga para maximizar a taxa de transferência. Esse processo torna as tarefas relativamente leves, e você pode criar muitas delas para permitir um paralelismo refinado.
Mais controle programático do que é possível com um thread ou item de trabalho.
As tarefas e a estrutura construída em torno delas fornecem um rico conjunto de APIs que suportam espera, cancelamento, continuações, tratamento robusto de exceções, status detalhado, agendamento personalizado e muito mais.
Por ambos os motivos, a TPL é a API preferida para escrever código multi-threaded, assíncrono e paralelo no .NET.
Criação e execução de tarefas implicitamente
O método Parallel.Invoke fornece uma maneira conveniente de executar qualquer número de instruções arbitrárias simultaneamente. Basta passar um Action delegado para cada item de trabalho. A maneira mais fácil de criar esses delegados é usar expressões lambda. A expressão lambda pode chamar um método nomeado ou fornecer o código embutido. O exemplo a seguir mostra uma chamada Invoke básica que cria e inicia duas tarefas que são executadas simultaneamente. A primeira tarefa é representada por uma expressão lambda que chama um método chamado DoSomeWork
, e a segunda tarefa é representada por uma expressão lambda que chama um método chamado DoSomeOtherWork
.
Observação
Esta documentação usa expressões lambda para definir delegados na TPL. Se não estiver familiarizado com expressões lambda em C# ou Visual Basic, consulte expressões lambda em PLINQ e TPL.
Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());
Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())
Observação
O número de instâncias de Task criadas em segundo plano por Invoke não é necessariamente igual ao número de delegados fornecidos. O TPL pode empregar várias otimizações, especialmente com um grande número de delegados.
Para obter mais informações, consulte Como usar Parallel.Invoke para executar operações paralelas.
Para maior controle sobre a execução da tarefa ou para retornar um valor da tarefa, você deve trabalhar com Task objetos de forma mais explícita.
Criando e executando tarefas explicitamente
Uma tarefa que não retorna um valor é representada pela classe System.Threading.Tasks.Task. Uma tarefa que retorna um valor é representada pela classe System.Threading.Tasks.Task<TResult>, que herda de Task. O objeto de tarefa lida com os detalhes da infraestrutura e fornece métodos e propriedades que são acessíveis a partir do thread de chamada durante todo o tempo de vida da tarefa. Por exemplo, você pode acessar a propriedade Status de uma tarefa a qualquer momento para determinar se ela começou a ser executada, foi executada até a conclusão, foi cancelada ou lançou uma exceção. O status é representado por uma enumeração TaskStatus.
Ao criar uma tarefa, você dá a ela um delegado de usuário que encapsula o código que a tarefa executará. O delegado pode ser expresso como um delegado nomeado, um método anônimo ou uma expressão lambda. As expressões do Lambda podem conter uma chamada para um método nomeado, conforme mostrado no exemplo a seguir. O exemplo inclui uma chamada para o método Task.Wait para garantir que a tarefa conclua a execução antes que o aplicativo de modo de console termine.
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
Você também pode usar os métodos Task.Run para criar e iniciar uma tarefa em uma operação. Para gerenciar a tarefa, os métodos Run usam o agendador de tarefas padrão, independentemente de qual agendador de tarefas está associado ao thread atual. Os métodos Run são a maneira preferida de criar e iniciar tarefas quando não é necessário mais controle sobre a criação e o agendamento da tarefa.
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
Você também pode usar o método TaskFactory.StartNew para criar e iniciar uma tarefa em uma operação. Como mostrado no exemplo a seguir, você pode usar esse método quando:
A criação e o agendamento não precisam ser separados e você precisa de opções adicionais de criação de tarefas ou o uso de um agendador específico.
Você precisa passar um estado adicional para a tarefa que você pode recuperar por meio de sua propriedade 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 e Task<TResult> expõem uma propriedade Factory estática que retorna uma instância padrão de TaskFactory, para que você possa chamar o método como Task.Factory.StartNew()
. Além disso, no exemplo a seguir, como as tarefas são do tipo System.Threading.Tasks.Task<TResult>, cada uma delas tem uma propriedade Task<TResult>.Result pública que contém o resultado da computação. As tarefas são executadas de forma assíncrona e podem ser concluídas em qualquer ordem. Se a propriedade Result for acessada antes da conclusão do cálculo, a propriedade bloqueará o thread de chamada até que o valor esteja disponível.
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
Para obter mais informações, consulte Como retornar um valor de uma tarefa.
Ao usar uma expressão lambda para criar um delegado, você tem acesso a todas as variáveis visíveis nesse ponto do código-fonte. No entanto, em alguns casos, principalmente dentro de loops, um lambda não captura a variável como esperado. Ele captura apenas a referência da variável, não o valor, pois ele muda após cada iteração. O exemplo a seguir ilustra o problema. Ele passa um contador de loop para uma expressão lambda que instancia um objeto CustomData
e usa o contador de loop como identificador do objeto. Como mostra a saída do exemplo, cada objeto CustomData
tem um identificador idêntico.
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
Você pode acessar o valor em cada iteração fornecendo um objeto de estado para uma tarefa por meio de seu construtor. O exemplo a seguir modifica o exemplo anterior usando o contador de loop ao criar o objeto CustomData
, que, por sua vez, é passado para a expressão lambda. Como mostra a saída do exemplo, cada objeto CustomData
agora tem um identificador exclusivo baseado no valor do contador de loop no momento em que o objeto foi instanciado.
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
Esse estado é passado como um argumento para o delegado de tarefa e pode ser acessado a partir do objeto de tarefa usando a propriedade Task.AsyncState. O exemplo a seguir é uma variação do exemplo anterior. Ele usa a propriedade AsyncState para exibir informações sobre os objetos CustomData
passados para a expressão lambda.
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
ID da tarefa
Cada tarefa recebe um ID inteiro que a identifica exclusivamente em um domínio de aplicativo e pode ser acessada usando a propriedade Task.Id. O ID é útil para visualizar informações de tarefas nas janelas do depurador do Visual Studio Parallel Stacks e Tarefas. O ID é criado preguiçosamente, o que significa que não é criado até ser solicitado. Portanto, uma tarefa pode ter um ID diferente cada vez que o programa é executado. Para obter mais informações sobre como exibir IDs de tarefas no depurador, consulte Utilizar a Janela Tarefas e Utilizar a Janela Pilhas Paralelas.
Opções de criação de tarefas
A maioria das APIs que criam tarefas fornece sobrecargas que aceitam um parâmetro TaskCreationOptions. Ao especificar uma ou mais dessas opções, você informa ao agendador de tarefas como agendar a tarefa no pool de threads. As opções podem ser combinadas usando uma operação bit a bit OU.
O exemplo a seguir mostra uma tarefa que tem as opções LongRunning e 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()
Tarefas, threads e cultura
Cada thread tem uma cultura associada e uma cultura da interface do usuário, que são definidas pelas propriedades Thread.CurrentCulture e Thread.CurrentUICulture, respectivamente. A cultura de um thread é usada em operações como formatação, análise, classificação e operações de comparação de strings. A cultura de interface de uma thread é usada na pesquisa de recursos.
A cultura do sistema define a cultura padrão e a cultura da interface do usuário de um thread. No entanto, você pode especificar uma cultura padrão para todos os threads em um domínio de aplicativo usando as propriedades CultureInfo.DefaultThreadCurrentCulture e CultureInfo.DefaultThreadCurrentUICulture. Se você definir explicitamente a cultura de um thread e iniciar um novo thread, o novo thread não herdará a cultura do thread de chamada; em vez disso, sua cultura é a cultura padrão do sistema. No entanto, na programação baseada em tarefas, as tarefas usam a cultura do thread de chamada, mesmo que a tarefa seja executada de forma assíncrona em um thread diferente.
O exemplo a seguir fornece uma ilustração simples. Ele muda a cultura atual do aplicativo para francês (França). Se o francês (França) já é a cultura atual, ele muda para o inglês (Estados Unidos). Em seguida, ele invoca um delegado chamado formatDelegate
que retorna alguns números formatados como valores de moeda na nova cultura. Se o delegado é invocado por uma tarefa de forma síncrona ou assíncrona, a tarefa usa a cultura do thread de chamada.
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 €
Observação
Em versões do .NET Framework anteriores ao .NET Framework 4.6, a cultura de uma tarefa é determinada pela cultura do thread no qual ela é executada, não pela cultura do thread de chamada. Para tarefas assíncronas, a cultura usada pela tarefa pode ser diferente da cultura do thread de chamada.
Para obter mais informações sobre tarefas assíncronas e cultura, consulte a seção "Cultura e operações baseadas em tarefas assíncronas" no artigo CultureInfo.
Criando continuações de tarefas
Os métodos Task.ContinueWith e Task<TResult>.ContinueWith permitem especificar uma tarefa a ser iniciada quando a tarefa antecedente concluída. O delegado da tarefa de continuação recebe uma referência à tarefa antecedente para que possa examinar o status da tarefa antecedente. E ao recuperar o valor da propriedade Task<TResult>.Result, pode utilizar a saída do antecedente como entrada para a continuação.
No exemplo a seguir, a tarefa getData
é iniciada por uma chamada para o método TaskFactory.StartNew<TResult>(Func<TResult>). A tarefa processData
é iniciada automaticamente quando getData
termina e displayData
é iniciada quando processData
termina.
getData
produz uma matriz inteira, que é acessível à tarefa processData
por meio da propriedade Task<TResult>.Result da tarefa getData
. A tarefa processData
processa essa matriz e retorna um resultado cujo tipo é inferido do tipo de retorno da expressão lambda passada para o método Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>). A tarefa displayData
é executada automaticamente quando processData
é concluída e o objeto Tuple<T1,T2,T3> retornado pela expressão lambda processData
fica acessível à tarefa displayData
por meio da propriedade Task<TResult>.Result da tarefa processData
. A tarefa displayData
recebe o resultado da tarefa processData
. Ele produz um resultado cujo tipo é inferido de maneira semelhante, e que é disponibilizado ao programa na propriedade 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
Como Task.ContinueWith é um método de instância, você pode encadear chamadas de método juntas em vez de instanciar um objeto Task<TResult> para cada tarefa antecedente. O exemplo a seguir é funcionalmente idêntico ao anterior, exceto que ele encadeia chamadas para o método Task.ContinueWith. O objeto Task<TResult> retornado pela cadeia de chamadas de método é a tarefa de continuação final.
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
Os métodos ContinueWhenAll e ContinueWhenAny permitem prosseguir a partir de várias tarefas.
Para obter mais informações, consulte encadeamento de tarefas usando tarefas de continuação.
Criando subtarefas independentes
Quando o código de usuário em execução em uma tarefa cria uma nova tarefa e não especifica a opção AttachedToParent, a nova tarefa não é sincronizada com a tarefa pai de nenhuma maneira especial. Esse tipo de tarefa não sincronizada é chamado de tarefa aninhada destacada ou tarefa filha destacada. O exemplo a seguir mostra uma tarefa que cria uma tarefa filho desanexada:
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.
Observação
A tarefa pai não espera que a tarefa filho desanexada seja concluída.
Criando tarefas filhas
Quando o código de usuário em execução em uma tarefa cria uma tarefa com a opção AttachedToParent, a nova tarefa é conhecida como uma tarefa filho anexada da tarefa pai. Você pode usar a opção AttachedToParent para expressar paralelismo de tarefa estruturada porque a tarefa pai espera implicitamente que todas as tarefas filhas anexadas terminem. O exemplo a seguir mostra uma tarefa pai que cria 10 tarefas secundárias anexadas. O exemplo chama o método Task.Wait para esperar que a tarefa principal termine. Não é necessário esperar que as tarefas filhas anexadas sejam explicitamente concluídas.
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
Uma tarefa pai pode usar a opção TaskCreationOptions.DenyChildAttach para impedir que outras tarefas sejam anexadas à tarefa pai. Para obter mais informações, consulte Tarefas subordinadas anexadas e desanexadas.
Aguardando a conclusão das tarefas
Os tipos System.Threading.Tasks.Task e System.Threading.Tasks.Task<TResult> fornecem várias sobrecargas dos métodos Task.Wait que permitem aguardar a conclusão de uma tarefa. Além disso, as sobrecargas dos métodos estáticos Task.WaitAll e Task.WaitAny permitem aguardar a conclusão de qualquer uma ou de todas as tarefas de uma série de tarefas.
Normalmente, você esperaria por uma tarefa por um destes motivos:
O thread principal depende do resultado final calculado por uma tarefa.
Você tem que lidar com exceções que podem ser lançadas da tarefa.
O aplicativo pode ser encerrado antes que todas as tarefas tenham concluído a execução. Por exemplo, os aplicativos de console serão encerrados depois que todo o código síncrono em
Main
(o ponto de entrada do aplicativo) for executado.
O exemplo a seguir mostra o padrão básico que não envolve o tratamento de exceções:
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...
Para obter um exemplo que mostra o tratamento de exceções, consulte Exception Handling.
Algumas sobrecargas permitem especificar um tempo limite, enquanto outras aceitam um parâmetro de entrada adicional CancellationToken para que a espera possa ser cancelada programaticamente ou em resposta à entrada do utilizador.
Quando você espera por uma tarefa, você implicitamente espera por todos os filhos dessa tarefa que foram criados usando a opção TaskCreationOptions.AttachedToParent. Task.Wait retorna imediatamente se a tarefa já tiver sido concluída. Um método Task.Wait lançará quaisquer exceções geradas por uma tarefa, mesmo que o método Task.Wait tenha sido chamado após a conclusão da tarefa.
Composição de tarefas
As classes Task e Task<TResult> fornecem vários métodos para ajudá-lo a compor várias tarefas. Esses métodos implementam padrões comuns e fazem melhor uso dos recursos de linguagem assíncrona fornecidos pelo C#, Visual Basic e F#. Esta seção descreve os métodos WhenAll, WhenAny, Delaye FromResult.
Task.WhenAll
O método Task.WhenAll aguarda de forma assíncrona a conclusão de vários objetos Task ou Task<TResult>. Ele fornece versões sobrecarregadas que permitem aguardar conjuntos não uniformes de tarefas. Por exemplo, você pode aguardar a conclusão de vários objetos Task e Task<TResult> a partir de uma chamada de método.
Tarefa.QuandoQualquer
O método Task.WhenAny aguarda de forma assíncrona a conclusão de um dos vários objetos Task ou Task<TResult>. Como no método Task.WhenAll, este método fornece versões sobrecarregadas que permitem que aguarde conjuntos não uniformes de tarefas. O método WhenAny é especialmente útil nos seguintes cenários:
Operações redundantes: Considere um algoritmo ou operação que pode ser executada de várias maneiras. Você pode usar o método WhenAny para selecionar a operação que termina primeiro e, em seguida, cancelar as operações restantes.
Operações intercaladas: Você pode iniciar várias operações que devem ser concluídas e usar o método WhenAny para processar resultados à medida que cada operação é concluída. Após a conclusão de uma operação, você pode iniciar uma ou mais tarefas.
Operações limitadas: Você pode usar o método WhenAny para estender o cenário anterior limitando o número de operações simultâneas.
Operações expiradas: Você pode usar o método WhenAny para selecionar entre uma ou mais tarefas e uma tarefa que termina após um tempo específico, como uma tarefa retornada pelo método Delay. O método Delay é descrito na seção a seguir.
Task.Delay
O método Task.Delay produz um objeto Task que termina após o tempo especificado. Você pode usar este método para criar loops que consultam dados, para especificar esperas, para retardar a manipulação da entrada do utilizador e assim por diante.
Tarefa(T).FromResult
Usando o método Task.FromResult, você pode criar um objeto Task<TResult> que contém um resultado pré-calculado. Esse método é útil quando você executa uma operação assíncrona que retorna um objeto Task<TResult> e o resultado desse objeto Task<TResult> já está calculado. Para obter um exemplo que usa FromResult para recuperar os resultados de operações de download assíncronas mantidas em um cache, consulte Como criar tarefas pré-computadas.
Tratamento de exceções em tarefas
Quando uma tarefa lança uma ou mais exceções, as exceções são encapsuladas em uma exceção AggregateException. Essa exceção é propagada de volta para o thread que se une à tarefa. Normalmente, é o thread aguardando a conclusão da tarefa ou o thread acessando a propriedade Result. Esse comportamento impõe a política do .NET Framework de que todas as exceções não tratadas por padrão devem encerrar o processo. O código de chamada pode lidar com as exceções usando qualquer um dos seguintes em um bloco de try
/catch
:
O thread de junção também pode lidar com exceções acessando a propriedade Exception antes que a tarefa seja coletada de lixo. Ao acessar essa propriedade, você impede que a exceção não tratada acione o comportamento de propagação de exceção que encerra o processo quando o objeto é finalizado.
Para obter mais informações sobre exceções e tarefas, consulte Exception Handling.
Cancelar tarefas
A classe Task suporta cancelamento cooperativo e é totalmente integrada com as classes System.Threading.CancellationTokenSource e System.Threading.CancellationToken, que foram introduzidas no .NET Framework 4. Muitos dos construtores na classe System.Threading.Tasks.Task tomam um objeto CancellationToken como um parâmetro de entrada. Muitas das sobrecargas de StartNew e Run também incluem um parâmetro CancellationToken.
Você pode criar o token e emitir a solicitação de cancelamento em algum momento posterior, usando a classe CancellationTokenSource. Passe o token para o Task como um argumento e também referencie o mesmo token no seu delegado de utilizador, que faz o trabalho de responder a uma solicitação de cancelamento.
Para obter mais informações, consulte Cancelamento de Tarefas e Como cancelar uma tarefa e os seus subtarefas.
A classe TaskFactory
A classe TaskFactory fornece métodos estáticos que encapsulam padrões comuns para criar e iniciar tarefas e tarefas de continuação.
O padrão mais comum é StartNew, que cria e inicia uma tarefa numa única instrução.
Quando se cria tarefas de continuação a partir de vários antecedentes, use o método ContinueWhenAll ou o método ContinueWhenAny ou seus equivalentes na classe Task<TResult>. Para obter mais informações, consulte Encadeamento de Tarefas Usando Tarefas de Continuação.
Para encapsular os métodos do modelo de programação assíncrona
BeginX
eEndX
numa instância Task ou Task<TResult>, utilize os métodos FromAsync. Para obter mais informações, consulte TPL e Programação assíncrona tradicional do .NET Framework.
O TaskFactory padrão pode ser acessado como uma propriedade estática na classe Task ou na classe Task<TResult>. Você também pode instanciar um TaskFactory diretamente e especificar várias opções que incluem um CancellationToken, uma opção TaskCreationOptions, uma opção TaskContinuationOptions ou um TaskScheduler. Quaisquer opções especificadas quando você cria a fábrica de tarefas serão aplicadas a todas as tarefas que ela criar, a menos que o Task seja criado usando a enumeração TaskCreationOptions, caso em que as opções da tarefa substituem as da fábrica de tarefas.
Tarefas sem delegados
Em alguns casos, talvez você queira usar um Task para encapsular alguma operação assíncrona executada por um componente externo em vez do seu representante de usuário. Se a operação for baseada no padrão Begin/End do modelo de programação assíncrona, você poderá usar os métodos FromAsync. Se esse não for o caso, pode usar o objeto TaskCompletionSource<TResult> para envolver a operação em uma tarefa e, assim, obter alguns dos benefícios da programabilidade Task. Por exemplo, suporte para propagação de exceções e continuações. Para obter mais informações, consulte TaskCompletionSource<TResult>.
Programadores personalizados
A maioria dos desenvolvedores de aplicativos ou bibliotecas não se importa em qual processador a tarefa é executada, como ela sincroniza o seu trabalho com outras tarefas ou como ela é agendada no System.Threading.ThreadPool. Eles só exigem que ele seja executado da forma mais eficiente possível no computador host. Se você precisar de um controle mais refinado sobre os detalhes de agendamento, o TPL permite que você defina algumas configurações no agendador de tarefas padrão e até mesmo forneça um agendador personalizado. Para obter mais informações, consulte TaskScheduler.
Estruturas de dados relacionadas
O TPL tem vários novos tipos públicos que são úteis em cenários paralelos e sequenciais. Isso inclui várias classes de coleção seguras para threads, rápidas e escaláveis no namespace System.Collections.Concurrent e vários novos tipos de sincronização. Por exemplo, System.Threading.Semaphore e System.Threading.ManualResetEventSlim, que são mais eficientes do que seus antecessores para tipos específicos de cargas de trabalho. Outros novos tipos no .NET Framework 4, por exemplo, System.Threading.Barrier e System.Threading.SpinLock, fornecem funcionalidades que não estavam disponíveis em versões anteriores. Para obter mais informações, consulte Data Structures for Parallel Programming.
Tipos de tarefas personalizados
Recomendamos que você não herde de System.Threading.Tasks.Task ou System.Threading.Tasks.Task<TResult>. Em vez disso, recomendamos que você use a propriedade AsyncState para associar dados ou estados adicionais a um objeto Task ou Task<TResult>. Você também pode usar métodos de extensão para estender a funcionalidade das classes Task e Task<TResult>. Para obter mais informações sobre métodos de extensão, consulte Extension Methods e Extension Methods.
Se você precisar herdar de Task ou Task<TResult>, não poderá usar Run ou as classes System.Threading.Tasks.TaskFactory, System.Threading.Tasks.TaskFactory<TResult>ou System.Threading.Tasks.TaskCompletionSource<TResult> para criar instâncias do seu tipo de tarefa personalizado. Você não pode usá-los porque essas classes criam apenas Task e Task<TResult> objetos. Além disso, você não pode usar os mecanismos de continuação de tarefas fornecidos por Task, Task<TResult>, TaskFactorye TaskFactory<TResult> para criar instâncias do seu tipo de tarefa personalizado. Você não pode usá-los porque essas classes também criam apenas objetos Task e Task<TResult>.
Secções relacionadas
Título | Descrição |
---|---|
Encadeamento de Tarefas Usando Tarefas de Continuação | Descreve como as continuações funcionam. |
Tarefas de criança anexadas e separadas | Descreve a diferença entre tarefas subordinadas anexadas e separadas. |
Cancelamento de Tarefas | Descreve o suporte a cancelamento que está incorporado no objeto Task. |
Tratamento de exceções | Descreve como as exceções em threads simultâneos são tratadas. |
Como: Usar Parallel.Invoke para executar operações paralelas | Descreve como usar Invoke. |
Como retornar um valor de uma tarefa | Descreve como retornar valores de tarefas. |
Como cancelar uma tarefa e suas subtarefas | Descreve como cancelar tarefas. |
Como criar tarefas pré-computadas | Descreve como usar o método Task.FromResult para recuperar os resultados de operações de download assíncrono que são mantidas em um cache. |
Como atravessar uma árvore binária com tarefas paralelas | Descreve como usar tarefas para atravessar uma árvore binária. |
Como Desembrulhar uma Tarefa Aninhada | Demonstra como usar o método de extensão Unwrap. |
paralelismo de dados | Descreve como usar For e ForEach para criar loops paralelos sobre dados. |
Programação paralela | Nó de nível superior para a programação paralela no .NET Framework. |