.NET

21 jun, 2013

C# – Apresentando TASKS

Publicidade

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

Uma tarefa (ou task) representa uma unidade de trabalho que deverá ser realizada. Essa unidade de trabalho pode rodar em uma thread separada e é também possível iniciar uma task de forma sincronizada que resulta em uma espera pela thread chamada. Com tarefas, 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 hierarquia em que uma tarefa pai pode criar novas tarefas filhas, que podem 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. Isso é semelhante ao que você viu na classe Thread.

No exemplo abaixo, o método é definido sem parâmetro e a ID da tarefa é exibida 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, em que 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, em vez de invocar o método Start(), você pode invocar o método RunSynchronously(). Dessa forma, a tarefa é iniciada também, mas ela está sendo executada na thread atual do chamador, que 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 a 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 tarefa 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.

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

Tradicionalmente, isso 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, e são muito eficientes e flexíveis. Por exemplo, você pode:

  • passar dados da antecedente para a continuação;
  • especificar as condições precisas em que a continuação será ou não invocada;
  • cancelar uma continuação ou antes de começar ou cooperativamente quando ela estiver sendo executada;
  • fornecer dicas sobre como a continuação deve ser agendada;
  • invocar múltiplas continuações da mesma antecedente;
  • invocar uma continuação quando todas ou qualquer uma das múltiplas antecedentes completarem;
  • vincular continuações uma após a outra a qualquer comprimento arbitrário;
  • usar uma continuação para manipular exceções lançadas pela antecedente.

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();
        }
    }
}

 

c_task2

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();
        }
    }
}

 

c_task1

 

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 criadas 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 dá suporte para cancelamento cooperativo e é totalmente integrada com a classe System.Threading.CancellationTokenSource e com a classe System.Threading.CancellationToken, que são novas 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.