C# - Apresentando Tasks


 A plataforma .NET na versão 4.0 apresenta o novo namespace System.Threading.Tasks, o qual contém classes que permitem abstrair a funcionalidade de threading onde, na verdade, por trás dos panos, uma ThreadPool é usada.

Uma tarefa (ou task) representa uma unidade de trabalho que deverá ser realizada. Esta unidade de trabalho pode rodar em uma thread separada e é também possível iniciar uma task de forma sincronizada a qual resulta em uma espera pela thread chamada. Com tasks, você tem uma camada de abstração mas também um bom controle sobre as threads relacionadas.

As tarefas (tasks) permitem muito mais flexibilidade na organização do trabalho que você precisa fazer. Por exemplo, você pode definir continuar o trabalho, que deve ser feito depois que uma tarefa esteja completa.

Isso pode diferenciar se um tarefa foi executada com sucesso ou não. Você também pode organizar as tarefas em uma hierarquia onde uma tarefa pai pode criar novas tarefas filhas que pode criar dependências e assim o cancelamento da tarefa pai também cancela suas tarefas filhas.

Iniciando Tasks

Para iniciar uma tarefa, você pode usar a classe TaskFactory ou o construtor da classe Task e o método Start().

O construtor Task lhe dá mais flexibilidade na criação da tarefa. Ao iniciar uma tarefa, uma instância da classe Task pode ser criada e o código que deve ser executado pode ser atribuído com uma Action ou delegate Action<object> tanto sem parâmetro como com um parâmetro object. Isto é semelhante ao que você viu na classe Thread.

No exemplo abaixo o método é definido sem parâmetro e ID da tarefa é exibido no Console:

using System.Threading.Tasks;

    static void MetodoTask()
    {
        Console.WriteLine("executando uma tarefa (task)");
        Console.WriteLine("Task id: {0}", Task.CurrentId);
    }

Instâncias de tarefas podem ser criadas de várias maneiras. A abordagem mais comum é utilizar o tipo de propriedade Factory do tipo Task para recuperar uma instância TaskFactory que pode ser usada para criar tarefas para vários propósitos. Por exemplo, para criar uma tarefa que executa uma ação, o método factory StartNew pode ser usado:

using System;
using System.Threading.Tasks;

namespace Usando_Task
{
    class Program
    {
        static void Main(string[] args)
        {
            var t = Task.Factory.StartNew(() => FazerAlgo());
            Console.ReadKey();
        }
        static void FazerAlgo()
        {
            Console.WriteLine("executando uma tarefa => FazerAlgo() (task)");
        }
    }
}

Existem maneiras diferentes para iniciar uma nova tarefa.

A primeira maneira é instanciando uma classe TaskFactory, onde o método MetodoTarefa é passado para o método StartNew(), e a tarefa é iniciada imediatamente:

  // usando factory task
  TaskFactory tf = new TaskFactory();
  Task t1 = tf.StartNew(MetodoTarefa);
 
  // usando a factor task via task
  Task t2 = Task.Factory.StartNew(MetodoTarefa);

A segunda abordagem usa o construtor da classe de Task. Quando o objeto Task é instanciado, a tarefa não será executada imediatamente. Em vez disso, a ela é dado o status Created. A tarefa é, então iniciada pela chamada do método Start() da classe Task.


  // usando o construtor Task
  Task t3 = new Task(MetodoTarefa);
  t3.Start();
 

Com a classe de Task, ao invés de invocar o método Start(), você pode invocar o método RunSynchronously(). Desta forma, a tarefa é iniciada também, mas ela está sendo executada na thread atual do chamador, o chamador precisa esperar até que a tarefa termine. Por padrão, a tarefa é executada de forma assíncrona.

A classe Task também fornece construtores que inicializam a tarefa, mas que não a agendam para execução. Por razões de desempenho, o método StartNew da classe TaskFactory deve ser o mecanismo preferido para criação e programação de tarefas, mas, para situações em que a criação e programação devem ser separadas, os construtores podem ser usados, e o método Start() da tarefa pode então ser utilizado para programar a tarefa para execução em um momento posterior.

Para as operações que retornam valores a classe Task<TResult> deve ser usada.

Continuando Tarefas

Usando a classe Tasks você pode especificar que, depois que uma tarefa for concluída outra tarefa específica deve começar a ser executada, por exemplo, uma nova tarefa que usa um resultado da anterior ou que deve fazer uma limpeza se a tarefa anterior falhou.

Considerando que o manipulador task ou não tem parâmetro ou tem um parâmetro object, o manipulador de continuação tem um parâmetro do tipo Task. Aqui, você pode acessar informações sobre a tarefa de origem.

Na programação assíncrona, é muito comum para uma operação assíncrona, na conclusão, invocar uma segunda operação e passar os dados para ela.

Tradicionalmente, isto tem sido feito por meio de métodos de retorno. Na biblioteca Task Parallel , a mesma funcionalidade é fornecida por tarefas de continuação. Uma tarefa de continuação (também conhecida como uma continuação) é uma tarefa assíncrona que é invocada por outra tarefa, o que é conhecido como a antecedente, quando a antecedente termina.

As Continuações são relativamente fáceis de utilizar, mas são, muito eficientes e flexíveis. Por exemplo, você pode:

Podemos criar continuações usando o método Task.ContinueWith. O exemplo a seguir mostra o padrão básico, (por motivos de clareza, o tratamento de exceção é omitido).

using System;
using System.Threading.Tasks;

namespace Continuation_Tasks
{
    class Program
    {
        static void Main(string[] args)
        {
            // A tarefa antecedente. Pode tambem ser criada com Task.Factory.StartNew.
            Task<DayOfWeek> tarefaA = new Task<DayOfWeek>(() => DateTime.Today.DayOfWeek);

            // A continuacao. Seu delegate toma a tarefa antecedente
            // como um argumento e pode retornar um tipo diferente

            Task<string> continuacao = tarefaA.ContinueWith((antecedent) =>
            {
                return String.Format("Hoje é {0}.",antecedent.Result);
            });

            // Iniciar a antecedente
            tarefaA.Start();

            // Usar o resultada da continuacao
            Console.WriteLine(continuacao.Result);
            Console.ReadKey();
        }
    }
}

Também é possível criar uma continuação multitarefa que será executada quando qualquer uma ou todas as tarefas de um array de tarefas tiverem sido completadas, como mostrado a seguir:

 using System;
using System.Threading.Tasks;

namespace Continuation_Tasks2
{
    class Program
    {
        static void Main(string[] args)
        {
            Task<int>[] tarefas = new Task<int>[2];
            tarefas[0] = new Task<int>(() =>
            {
                // faz alguma coisa... 
                return 34;
            });

            tarefas[1] = new Task<int>(() =>
            {
                // faz alguma coisa... 
                return 8;
            });

            var continuation = Task.Factory.ContinueWhenAll(
                            tarefas,
                            (antecedents) =>
                            {
                                int resposta = tarefas[0].Result + tarefas[1].Result;
                                Console.WriteLine("A resposta é {0}", resposta);          
                            });

            tarefas[0].Start();
            tarefas[1].Start();
            continuation.Wait();
            Console.ReadKey();
        }
    }
}

O método Task.WhenAll aguarda assincronamente múltiplas operações assíncronas que são representadas através de uma coleção de tarefas. Usamos o método Task.WhenAll em um conjunto de tarefas. A aplicação de WhenAll retorna uma única tarefa que não está completa até que cada tarefa na coleção seja concluída. As tarefas parecem ser executadas em paralelo, mas não são criados novas threads.

Uma continuação é criada no estado WaitingForActivation e, portanto, só pode ser iniciada por sua tarefa antecedente. Chamar Task.Start em uma continuação no código do usuário levanta uma exceção System.InvalidOperationException.

A continuação é por si só uma tarefa e não bloqueia a thread na qual ela é iniciada. Use o método Wait para bloquear até a tarefa da continuação terminar.

A classe Task da suporte a cancelamento cooperativo e é totalmente integrado com a classe System.Threading.CancellationTokenSource e com a classe System.Threading.CancellationToken, que são novos no Framework 4. NET.

Muitos dos construtores da classe System.Threading.Tasks.Task tomam um CancellationToken como parâmetro de entrada. Muitas das sobrecargas StartNew e Run também possuem um CancellationToken.

Rom 1:16 Porque não me envergonho do evangelho, pois é o poder de Deus para salvação de todo aquele que crê; primeiro do judeu, e também do grego.

Referências:


José Carlos Macoratti