Ao contrário do que temos na classe Thread, a classe Task não fornece um método para abortar a execução da mesma. Se desejamos “monitorar” a execução e ter a chance de cancelar, temos que passar uma espécie de token ao criar a tarefa, e externamente, quando quisermos, podemos cancelar a execução. Compete aquele que programa a tarefa a ser executada, avaliar se o cancelamento foi ou não solicitado.
No .NET Framework a classe que nos permite controlar o cancelamento é a CancellationTokenSource. Ela expõe uma propriedade chamada Token que por sua vez, é representada pela classe CancellationToken, e é este valor que temos que passar para a tarefa a ser executada, e através do método Cancel podemos solicitar o cancelamento. Um detalhe importante aqui é que a classe CancellationTokenSource também disponibiliza um método estático chamado CreateLinkedTokenSource, que nos permite agrupar vários tokens, e quando qualquer um deles for cancelado, a tarefa é cancelada.
var cts1 = new CancellationTokenSource();
var cts2 = new CancellationTokenSource();
using (var cts3 = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token))
{
var task = Task.Factory.StartNew(() =>
{
for (int i = 0; i < int.MaxValue; i++)
{
cts3.Token.ThrowIfCancellationRequested();
Console.WriteLine(i);
}
}, cts3.Token);
Console.ReadLine();
cts2.Cancel();
Console.ReadLine();
}
Repare que criamos dois tokens (cts1 e cts2) e agrupamos em um terceiro chamado cts3. Se invocarmos o método Cancel de qualquer um dos três, a tarefa será interrompida. Vale lembrar que somente o fato de passar o token na criação da tarefa não é suficiente para interromper a execução; como foi dito acima, é de responsabilidade do desenvolver monitorar a solicitação de cancelamento, e para isso, neste caso estamos utilizando o método ThrowIfCancellationRequested, que dispara uma exceção se a solicitação foi feita.
Por fim, note que apenas o terceiro CancellationTokenSource está sendo envolvido em um bloco using. Isso é porque quando criamos este objeto a partir do link entre outros, o método Dispose irá executar algumas atividades que irão impactar a memória, descartando objetos que são criados exclusivamente para isso. O método Dispose nesta classe também pode ser útil quando estamos utilizando a funcionalidade CancelAfter, que utiliza internamente um Timer para monitorar o tempo e, automaticamente, interromper a tarefa que está – ainda – sendo executada depois que o tempo expira.