VB .NET - Apresentando o conceito de Task

Neste artigo vou apresentar o conceito de Task disponível a partir da versão 4.0 da plataforma .NET usando a linguagem VB .NET.

A partir da versão 4.0 da plataforma .NET  temos acesso ao 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, que é uma operação assíncrona, 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 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 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 Task

O classe Task representa uma única operação que não retorna um valor e que geralmente é executado de maneira assíncrona. Ela possui 5 sobrecarga para seus construtores.

O construtor mais usado é : Task(Action) que inicializa uma nova task com uma ação especificada.

As principais propriedades da classe Task são:

Nome Descrição
AsyncState
Obtém o estado do objeto fornecido quando a Task foi criada, ou é nulo se nada for fornecido.
CompletedTask
Obtém uma tarefa que já foi concluída com êxito.
CreationOptions
Obtém o TaskCreationOptions usado para criar esta tarefa.
CurrentId
Retorna o ID da Task atualmente em execução.
Exception
Obtém o AggregateException que causou o encerramento da  Task.
Se a Task foi concluída com êxito ou não ainda acionou todas as exceções, retornará null.
Factory
Fornece acesso a métodos de fábrica para criar e configurar Task e Task(Of TResult) instâncias.
Id
Obtém uma identificação(ID)  para a instância da Task.
IsCanceled
Obtém se a instância da Task concluiu a execução devido a estar sendo cancelada
IsCompleted
Obtém se a Task foi concluída.
IsFaulted
Obtém se a Task foi concluída devido a uma exceção sem tratamento.
Status
Obtém o TaskStatus dessa tarefa.

fonte: https://msdn.microsoft.com/pt-br/library/system.threading.tasks.task%28v=vs.110%29.aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1

Os métodos mais importantes da classe Task são:

Nome Descrição
ContinueWith()  Cria uma continuação que recebe informações de estado fornecido pelo chamador e é executada quando a Task de destino é concluída.
Delay(int32)
 Cria uma tarefa  que é concluída após um tempo
Run(Action)
 Enfileira o trabalho especificado para ser executado no ThreadPool e retorna um identificador de tarefa para esse trabalho
Start()
 Inicia a Task, agendando-a  para execução no TaskScheduler atual
Wait()
 Espera até que a Task conclua
WaitAll()  Aguarda todos objetos Task fornecidos concluir a execução
WaitAny()
 Aguarda  qualquer um dos objetos Task fornecidos  concluir a execução.
WhenAll()
 Cria uma tarefa que será concluída quando todos os objetos Task em uma coleção enumerável foram concluídos.
WhenAny
 Cria uma tarefa que será concluída quando qualquer tarefa fornecida foi concluída.

Nota: Apresentei aqui apenas um breve resumo. Para detalhes confira : https://msdn.microsoft.com/pt-br/library/system.threading.tasks.task%28v=vs.110%29.aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1

Vamos agora mostrar alguns exemplos de utilização da classe Task.

Recursos usados:

Nota: Baixe e use a versão Community 2015 do VS ela é grátis e é equivalente a versão Professional.

Criando a solução no VS Community

Abra o VS Community 2015 e clique em New Project;

Selecione a linguagem Visual Basic e o template Console Application;

Informe o nome VBNET_Tasks e clique no botão OK;

1 - 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 é muito semelhante ao que você já viu na classe Thread.

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:

Module Module1
    Sub Main()
        Dim t = Task.Factory.StartNew(Sub() FazerAlgo())
        Console.ReadKey()
    End Sub
    Private Sub FazerAlgo()
        Console.WriteLine("executando uma tarefa => FazerAlgo() (task)")
    End Sub
End Module

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:

   Sub Main()
        ' usando factory task
        Dim tf As New TaskFactory()
        Dim t1 As Task = tf.StartNew(Sub() FazerAlgo())
   End Sub

O método StartNew cria e inicia a Task.

Usar StartNew é funcionalmente equivalente a criar uma tarefa usando um dos seus construtores e, em seguida, chamar Start para programá-la para execução.

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.

    Sub Main()
        ' usando o construtor Task
        Dim tf3 As New Task(Sub() FazerAlgo())
        tf3.Start()
        Console.ReadKey()
    End Sub

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(Of Resultado> deve ser usada.

O método Run fornece uma maneira simples de iniciar uma tarefa usando valores padrão e sem a necessidade de parâmetros adicionais.  

O exemplo a seguir usa o método Run(Action) para iniciar uma tarefa que executa um loop e exibe o número de iterações de loop:  

Module Module1
    Sub Main()
        Dim t As Task = Task.Run(Sub()
                                     'Apenas um loop
                                     Dim ctr As Integer = 0
                                     For ctr = 0 To 999999
                                     Next
                                     Console.WriteLine("Finalizado {0} iterações do loop", ctr)
                                 End Sub)
        'espera a tarefa completar a execução
        t.Wait()
        Console.WriteLine("Operação concluída...")
        Console.ReadKey()
    End Sub
End Module

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.

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, contudo, 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).

Module Module1
    Sub Main()
        ' 1-) A tarefa antecedente. 
        ' Pode tambem ser criada com Task.Factory.StartNew.
        Dim tarefa1 As New Task(Of DayOfWeek)(Function() DateTime.Today.DayOfWeek)
        ' 2-) A continuacao. 
        ' Seu delegate toma a tarefa antecedente como um argumento e pode retornar um tipo diferente
        Dim continuacao As Task(Of String) = tarefa1.ContinueWith(Function(antecedente)
                                                                      Return [String].Format("Hoje é {0}.", antecedente.Result)
                                                                  End Function)
        ' Iniciar a antecedente
        tarefa1.Start()
        ' Usar o resultado da continuacao
        Console.WriteLine(continuacao.Result)
        Console.ReadKey()
    End Sub
End Module

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:

Module Module1
    Sub Main()
        'Cria um array de tarefas
        Dim tarefas As Task(Of Integer)() = New Task(Of Integer)(1) {}
        'define a primeira tarefa para retornar 450
        tarefas(0) = New Task(Of Integer)(Function()
                                              ' faz alguma coisa... 
                                              Return 450
                                          End Function)
        'define a segunda tarefa para retornar 800
        tarefas(1) = New Task(Of Integer)(Function()
                                              ' faz alguma coisa... 
                                              Return 800
                                          End Function)
        'defina a continuacao usando o método ContinueWhenAll
        'obtendo os resultados das tarefas e retornando a soma
        Dim continuacao = Task.Factory.ContinueWhenAll(tarefas, Sub(antecedentes)
                                                                    Dim resposta As Integer = tarefas(0).Result + tarefas(1).Result
                                                                    Console.WriteLine("A resposta é {0}", resposta)
                                                                End Sub)
        'inicia a tarefa agendando a execução
        tarefas(0).Start()
        tarefas(1).Start()
        'aguarda a tarefa ser concluída
        continuacao.Wait()
        Console.WriteLine("Processamento concluído...") 
        Console.ReadKey()
    End Sub
End Module

O método ContinueWhenAll cria uma tarefa de continuação(executa o delegate continuationAction) que inicia quando o conjunto de tarefas especificadas foram concluídas, independente de seu status de conclusão.

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 integrada 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 e muitas das sobrecargas StartNew e Run também possuem um CancellationToken.

Aguarde em breve mais artigos abordando os recursos do conjunto de APIs da Task Parallel Library (TPL).

Pegue o projeto completo aqui:   VBNET_Tasks.zip

E os que são de Cristo crucificaram a carne com as suas paixões e concupiscências.
Gálatas 5:24

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Quer aprender a criar aplicações Web Dinâmicas usando a ASP .NET MVC 5 ?

 

  Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter

 

Referências:


José Carlos Macoratti