Programação assíncrona baseada em tarefas

A TPL (Biblioteca de Paralelismo de Tarefas) é baseada no conceito de uma tarefa, que representa uma operação assíncrona. De certa forma, uma tarefa é semelhante a um thread ou a um item de trabalho ThreadPool, mas em um nível mais alto de abstração. O termo paralelismo de tarefas refere-se a uma ou mais tarefas independentes que são executadas simultaneamente. As tarefas fornecem dois benefícios principais:

  • Uso mais eficiente e mais dimensionável de recursos do sistema.

    Nos bastidores, as tarefas são colocadas na fila ThreadPool, a qual foi aprimorada com algoritmos que determinam e se ajustam ao número de threads. Esses algoritmos fornecem balanceamento de carga para maximizar a taxa de transferência. Esse processo torna as tarefas relativamente simples, e você pode criar muitas delas para habilitar um paralelismo refinado.

  • Controle mais programático do que é possível com um thread ou um item de trabalho.

    As tarefas e a estrutura criada em torno delas fornecem um rico conjunto de APIs que oferecem suporte a espera, cancelamentos, continuações, tratamento de exceções robusto, status detalhado, agendamento personalizado e mais.

Por esses motivos, a TPL é a API preferida para escrever código multithread, assíncrono e código paralelo no .NET.

Criando e executando tarefas de forma implícita

O método Parallel.Invoke fornece uma maneira conveniente de executar simultaneamente um número qualquer de instruções arbitrárias. Basta passar um delegado Action 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 chamado ou fornecer o código embutido. O exemplo a seguir mostra uma chamada Invoke básica que cria e inicia duas tarefas executadas simultaneamente. A primeira tarefa é representada por uma expressão lambda que chama um método denominado DoSomeWork. A segunda tarefa é representada por uma expressão lambda que chama um método denominado DoSomeOtherWork.

Observação

Esta documentação usa expressões lambda para definir delegados na TLP. Se você não estiver familiarizado com expressões lambda no C# ou no Visual Basic, confira 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 que são criadas em segundo plano por Invoke não é necessariamente igual ao número de delegados que são fornecidos. A TPL pode usar várias otimizações, principalmente com números grandes de delegados.

Para obter mais informações, consulte Como usar Parallel.Invoke para executar operações em paralelo.

Para obter maior controle sobre a execução da tarefa ou para retornar um valor da tarefa, é necessário trabalhar com objetos Task de forma mais explícita.

Criando e executando tarefas de forma explícita

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>, a qual herda de Task. O objeto da tarefa lida com os detalhes da infraestrutura e fornece métodos e propriedades que são acessíveis a partir do segmento de chamada em 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, se foi executada até a conclusão, se foi cancelada ou se uma exceção foi gerada. O status é representado por uma enumeração TaskStatus.

Quando você cria uma tarefa, fornece a ela um delegado de usuário que encapsula o código que a tarefa irá executar. O delegado pode ser expresso como um delegado nomeado, um método anônimo ou uma expressão lambda. As expressões 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 termine a execução antes do aplicativo de modo console terminar.

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 única operação. Para gerenciar a tarefa, os métodos Run usam o agendador de tarefas padrão, independentemente de qual agendador de tarefas é associado ao segmento atual. Os métodos Run são a maneira preferencial para criar e iniciar tarefas quando mais controle sobre a criação e o agendamento da tarefa não é necessário.

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. Conforme 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 possui uma propriedade pública Task<TResult>.Result que contém o resultado do cálculo. As tarefas são executadas de forma assíncrona e podem ser concluídas em qualquer ordem. Se a propriedade Result for acessada antes que a computação seja concluída, a propriedade bloqueará o thread de chamada até o valor estar 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 que são visíveis nesse ponto em seu código-fonte. No entanto, em alguns casos, especialmente dentro de loops, um lambda não captura a variável conforme o esperado. Ele captura somente a referência da variável, não o valor no qual ele se transforma após cada iteração. O exemplo a seguir ilustra o problema. Ele passa um contador de loops para uma expressão lambda que instancia um objeto CustomData e usa o contador de loops como o identificador do objeto. Conforme mostra a saída do exemplo, cada objeto CustomData possui 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 ao fornecer um objeto de estado para uma tarefa através do respectivo construtor. O exemplo a seguir modifica o exemplo anterior usando o contador de loops ao criar o objeto CustomData, que, em seguida, é transmitido para a expressão lambda. Como mostra a saída do exemplo, cada objeto CustomData agora tem um identificador exclusivo com base no valor do contador de loops 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

Este estado é transmitido como um argumento para o delegado da tarefa e pode ser acessado do objeto de tarefa via 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 que pode ser acessado pela propriedade Task.Id. A ID é útil para exibir informações sobre a tarefa nas janelas Pilhas Paralelas e Tarefas do depurador do Visual Studio. A ID é criada lentamente, o que significa que ela não é criada até ser solicitada. Portanto, uma tarefa pode ter uma ID diferente sempre que o programa é executado. Para obter mais informações sobre como exibir IDs de tarefa no depurador, consulte Usando a janela Tarefas e Usando a janela Pilhas Paralelas.

Opções de criação de tarefa

A maioria das APIs que criam tarefas fornecem sobrecargas que aceitam um parâmetro TaskCreationOptions. Ao especificar uma ou mais dessas opções, você informa o agendador de tarefas como agendar a tarefa no pool de threads. As opções podem ser combinadas usando uma operação OR bit a bit.

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 de interface do usuário, que é definida 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 cadeia de caracteres. A cultura de interface do usuário de um 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 a cultura de um thread de forma explícita e iniciar um novo thread, o novo thread não herdará a cultura do thread de chamada; em vez disso, sua cultura será 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 maneira assíncrona em um thread diferente.

O exemplo a seguir fornece uma ilustração simples. Ele altera a cultura atual do aplicativo para francês (França). Se francês (França) já é a cultura atual, ele muda para inglês (Estados Unidos). Depois, ele chama um delegado chamado formatDelegate que retorna alguns números formatados como valores de moeda na nova cultura. Seja o delegado é invocado por uma tarefa de maneira 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 à .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, isso significa que a cultura usada pela tarefa pode ser diferente da cultura do thread de chamada.

Para saber mais sobre a cultura e tarefas assíncronas, veja a seção "Cultura e operações assíncronas baseadas em tarefas assíncronas" no artigo CultureInfo.

Criando continuações de tarefa

Os métodos Task.ContinueWith e Task<TResult>.ContinueWith permitem que você especifique uma tarefa para iniciar quando a tarefa antecedente terminar. 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, você pode usar a saída do antecedente como entrada para a continuação.

No exemplo a seguir, a tarefa getData é iniciada por uma chamada feita ao 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 de inteiros que é acessível para a tarefa processData por meio da propriedade getData da tarefa Task<TResult>.Result. A tarefa processData processa a matriz e retorna um resultado cujo tipo é derivado do tipo retornado pela expressão lambda passada ao método Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>). A tarefa displayData é executada automaticamente quando processData é concluído e o objeto Tuple<T1,T2,T3> retornado pela expressão lambda processData é acessível para a tarefa de displayData através da propriedade processData da tarefa Task<TResult>.Result. A tarefa displayData leva ao resultado da tarefa processData. Isso gera um resultado cujo tipo é inferido de maneira semelhante e que é disponibilizado para o 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, é possível encadear as chamadas de método juntas em vez de criar uma instância de um objeto de Task<TResult> para cada tarefa antecedente. O exemplo a seguir é funcionalmente idêntico ao exemplo anterior, exceto que ele encadeia chamadas ao método Task.ContinueWith. O objeto de Task<TResult> retornado pela cadeia de chamadas de métodos é a tarefa final de continuação.

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 que você continue de várias tarefas.

Para obter mais informações, consulte Encadeando tarefas com tarefas de continuação.

Criando tarefas filho desanexadas

Quando o código do 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 é chamada de tarefa aninhada desanexada ou tarefa filho desanexada. O exemplo a seguir mostra uma tarefa que cria uma tarefa filha 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 a tarefa filha desanexada terminar.

Criando tarefas filho

Quando o código do usuário que está executando 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 o paralelismo estruturado das tarefas, porque a tarefa pai espera implicitamente todas as tarefas filhas anexadas terminarem. O exemplo a seguir mostra uma tarefa pai que cria dez tarefas filhas anexadas. O exemplo chama o método Task.Wait para aguardar a conclusão da tarefa pai. Ele não precisa aguardar explicitamente a conclusão das tarefas filho anexadas.

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 se anexem à tarefa pai. Para obter mais informações, consulte Tarefas filho 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 que você aguarde a conclusão de qualquer uma ou de todas as matrizes de tarefas.

Normalmente, você esperaria uma tarefa por um destes motivos:

  • O thread principal depende do resultado final calculado por uma tarefa.

  • Você precisa tratar exceções que podem ser geradas pela tarefa.

  • O aplicativo pode terminar antes que todas as tarefas concluam a execução. Por exemplo, aplicativos de console terminarão após todos os códigos síncronos em Main (o ponto de entrada do aplicativo) tiverem sido executados.

O exemplo a seguir mostra o padrão básico que não envolve 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 Tratamento de exceções.

Algumas sobrecargas permitem especificar um tempo limite, e outras usam um CancellationToken adicional como um parâmetro de entrada para que a espera possa ser cancelada programaticamente ou em resposta a uma entrada do usuário.

Ao aguardar uma tarefa, você espera implicitamente todas as filhos da tarefa que foram criadas usando a opção TaskCreationOptions.AttachedToParent. Task.Wait retorna imediatamente se a tarefa já foi concluída. Um método Task.Wait lançará todas as exceções geradas por uma tarefa, mesmo que o método Task.Wait tenha sido chamado após a conclusão da tarefa.

Compondo tarefas

As classes Task e Task<TResult> fornecem vários métodos para ajudar a compor várias tarefas. Esses métodos implementam padrões comuns e usam melhor os recursos de linguagem assíncrona fornecidos por C#, Visual Basic e F#. Esta seção descreve os métodos WhenAll, WhenAny, Delay e FromResult.

Task.WhenAll

O método Task.WhenAll espera assincronamente 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 vários objetos Task e Task<TResult> para concluir após uma chamada de método.

Task.WhenAny

O método Task.WhenAny espera assincronamente a conclusão de um de vários objetos Task ou Task<TResult>. Assim como no método Task.WhenAll, esse método oferece as versões sobrecarregadas que permitem aguardar conjuntos não uniformes de tarefas. O método WhenAny é especialmente útil nos cenários a seguir:

  • 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 então cancelar as operações restantes.

  • Operações intercaladas: você pode iniciar várias operações que todos devem concluir e usar o método WhenAny para processar resultados à medida que cada operação termina. Após uma operação ser concluída, você poderá começar uma ou mais tarefas.

  • Operações restritas: 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 que é retornada pelo método Delay. O método Delay é descrito na seção a seguir.

Task.Delay

O método Task.Delay gera um objeto Task que se encerra após o tempo especificado. Você pode usar esse método para criar loops que pesquisam dados para especificar tempos limite, atrasar a manipulação da entrada do usuário entre outros.

Task(T).FromResult

Ao usar o método Task.FromResult, você pode criar um objeto Task<TResult> com um resultado pré-calculado. Este método é útil quando você executa uma operação assíncrona que retorna um objeto Task<TResult> e o resultado do objeto Task<TResult> já está calculado. Para obter um exemplo que use FromResult para recuperar os resultados de operações de download assíncronas mantidas em um cache, veja Como criar tarefas pré-computadas.

Tratando exceções em tarefas

Quando uma tarefa gera uma ou mais exceções, as exceções são envolvidas em uma exceção AggregateException. Essa exceção é propagada de volta para o thread que une a tarefa. Normalmente, é o thread que aguarda a conclusão da tarefa ou o thread que acessa a propriedade Result. Esse comportamento impõe a política do .NET Framework de que todas as exceções sem tratamento devem, por padrão, encerrar o processo. O código de chamada pode tratar as exceções usando uma destas opções em um bloco try/catch:

O thread de associação também pode manipular exceções ao acessar a propriedade Exception antes que a tarefa seja coletada pela lixeira. Ao acessar essa propriedade, você impede que a exceção sem tratamento dispare o comportamento de propagação de exceção que finaliza o processo quando o objeto é encerrado.

Para obter mais informações sobre exceções e tarefas, consulte Tratamento de exceções.

Cancelando tarefas

A classe Task oferece suporte ao cancelamento cooperativo e é totalmente integrada às classes System.Threading.CancellationTokenSource e System.Threading.CancellationToken, as quais foram introduzidas no .NET Framework 4. Muitos dos construtores na classe System.Threading.Tasks.Task recebem um objeto CancellationToken como um parâmetro de entrada. Várias das sobrecargas StartNew e Run também incluem um parâmetro CancellationToken.

Você pode criar o token e enviar a solicitação de cancelamento algum tempo depois usando a classe CancellationTokenSource. Passe o token para Task como um argumento e também referencie o mesmo token em seu delegado de usuário, o qual 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 seus filhos.

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 de linha.

O TaskFactory padrão pode ser acessado como uma propriedade estática na classe Task ou na classe Task<TResult>. Você também pode criar uma instância TaskFactory diretamente e especificar várias opções que incluem CancellationToken, uma opção TaskCreationOptions, uma opção TaskContinuationOptions ou um TaskScheduler. As opções especificadas quando você cria a fábrica de tarefas serão aplicadas a todas as tarefas criadas por ela, a menos que Task seja criada usando a enumeração TaskCreationOptions. Nesse caso, as opções de tarefa substituem as da fábrica de tarefas.

Tarefas sem representantes

Em alguns casos, convém usar Task para encapsular qualquer operação assíncrona executada por um componente externo em vez de seu delegado 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, você poderá usar o objeto TaskCompletionSource<TResult> para envolver a operação em uma tarefa e obter assim alguns benefícios de programabilidade de Task. Por exemplo, suporte para propagação de exceção e continuações. Para obter mais informações, consulte TaskCompletionSource<TResult>.

Agendadores personalizados

A maioria dos desenvolvedores de aplicativos ou bibliotecas não se importa com o processador em que a tarefa é executada, como ele sincroniza seu trabalho com outras tarefas e como ela é agendada em System.Threading.ThreadPool. Eles apenas exigem que ela seja executada da maneira mais eficiente possível no computador host. Se você exigir um controle mais aguçado sobre os detalhes de programação, a TPL permite definir algumas configurações no agendador de tarefas padrão e permite até mesmo fornecer um agendador personalizado. Para obter mais informações, consulte TaskScheduler.

A TPL possui vários novos tipos públicos que são úteis em cenários paralelos e sequenciais. Isso inclui várias classes de coleta seguras, 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 a funcionalidade que não estava disponível em versões anteriores. Para obter mais informações, consulte Estruturas de dados para programação paralela.

Tipos de tarefa personalizados

Recomendamos que essa herança não venha de System.Threading.Tasks.Task ou de System.Threading.Tasks.Task<TResult>. Em vez disso, recomenda-se usar a propriedade AsyncState para associar dados adicionais ou o estado a um objeto Task ou Task<TResult>. Você também pode usar os métodos de extensão para estender a funcionalidade de Task e Task<TResult>. Para obter mais informações sobre métodos de extensão, consulte Métodos de extensão e Métodos de extensão.

Se for necessário herdar de Task ou Task<TResult>, você não poderá usar as classes Run, System.Threading.Tasks.TaskFactory ou System.Threading.Tasks.TaskFactory<TResult>, System.Threading.Tasks.TaskCompletionSource<TResult> para criar instâncias de seu tipo de tarefa personalizada. Você não pode usá-las porque essas classes criam apenas objetos Task e Task<TResult>. Além disso, você não pode usar os mecanismos de continuação de tarefas fornecidos por Task, Task<TResult>, TaskFactory e TaskFactory<TResult> para criar instâncias de seu tipo de tarefa personalizada. Você não pode usá-las porque essas classes também criam somente objetos Task e Task<TResult>.

Título Descrição
Encadeando tarefas com tarefas de continuação Descreve como as continuações funcionam.
Tarefas filho anexadas e desanexadas Descreve a diferença entre tarefas filhas anexadas e desanexadas.
Cancelamento da tarefa Descreve o suporte a cancelamento interno do objeto Task.
Tratamento de exceção Descreve como as exceções são manipuladas em threads simultâneos.
Como: usar Parallel.Invoke para executar operações paralelas Descreve como usar o Invoke.
Como: Retornar um valor de uma tarefa Descreve como retornar valores de tarefas.
Como: Cancelar uma tarefa e seus filhos Descreve como cancelar tarefas.
Como: criar tarefas pré-computadas Descreve como usar o método Task.FromResult para recuperar os resultados das operações de download assíncronas armazenados em um cache.
Como: percorrer uma árvore binária com tarefas paralelas Descreve como usar tarefas para percorrer uma árvore binária.
Como: Desencapsular 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 do .NET Framework.

Confira também