C# - Cancelamento de operações assíncronas - I
Hoje veremos como cancelar operações assíncronas na linguagem C#. |
A plataforma .NET usa um modelo unificado para cancelamento cooperativo de operações assíncronas ou síncronas de longa duração. Este modelo é baseado em um objeto leve chamado token de cancelamento.
O objeto que invoca uma ou mais operações canceláveis, por exemplo, criando novas threads ou tarefas e passa o token para cada operação. As operações individuais podem, por sua vez, passar cópias do token para outras operações.
Mais tarde, o objeto que criou o token pode usá-lo para solicitar que as operações parem o que estão fazendo. Apenas o objeto solicitante pode emitir a solicitação de cancelamento, e cada ouvinte é responsável por perceber a solicitação e respondê-la de maneira adequada e oportuna.
O padrão geral para implementar o modelo de cancelamento cooperativo é o seguinte:
O framework de cancelamento é implementado como um conjunto de tipos relacionados, que estão listados na tabela a seguir.
Nome do tipo | DESCRIÇÃO |
---|---|
CancellationTokenSource | O objeto que cria um token de cancelamento, e também emite o pedido de cancelamento para todas as cópias desse token. |
CancellationToken | O tipo de valor leve passado a um ou mais ouvintes, normalmente como um parâmetro de método. Os ouvintes monitoram o valor da propriedade IsCancellationRequested do token por sondagem, retorno de chamada ou identificador de espera. |
OperationCanceledException | As sobrecargas do construtor desta exceção aceitam CancellationToken como um parâmetro. Os ouvintes podem, opcionalmente, lançar essa exceção para verificar a origem do cancelamento e notificar aos outros que ela respondeu a uma solicitação de cancelamento. |
Vejamos a seguir como podemos realizar o cancelamento na prática.
Recursos usados:
Cancelando uma tarefa assíncrona
Vamos iniciar criando um tarefa de longa duração definindo o método OperacaoLongaDuracao() que retorna um Task<int>:
private static Task<int> OperacaoLongaDuracao(int valor)
{
return Task.Run(() =>
{
int resultado = 0;
for (int i = 0; i < valor; i++)
{
Thread.Sleep(50);
resultado += i;
}
return resultado;
});
}
|
Aqui estou usando Thread.Sleep() que suspende a thread atual por um determinado tempo especificado em milissegundos para simular a longa duração.
A chamada deste método pode ser feita da seguinte forma:
static async Task Main(string[] args)
{
await ExecutaTaskAsync();
Console.ReadKey();
}
public static async Task ExecutaTaskAsync()
{
var stopwatch = new Stopwatch();
stopwatch.Start();
Console.WriteLine("Resultado {0}", await OperacaoLongaDuracao(100));
Console.WriteLine("Tempo gasto..." + stopwatch.Elapsed + "\n");
}
|
Ocorre que aqui não estamos permitindo o cancelamento da tarefa. Vamos implementar o cancelamento usando o padrão geral e o token de cancelamento.
A primeira coisa a fazer é tornar o nosso método de longa duração cancelável passando o CancellationToken para o método como uma parâmetro que aqui eu defini como opcional.
private static Task<int> OperacaoLongaDuracaoCancelavel(int valor, CancellationToken cancellationToken = default)
{
Task<int> task = null;
task = Task.Run(() =>
{
int resultado = 0;
for (int i = 0; i < valor; i++)
{
// Verifica se foi solicitado o cancelamento
// se foi lança um TaskCanceledException.
if (cancellationToken.IsCancellationRequested)
throw new TaskCanceledException(task);
Thread.Sleep(10);
resultado += i;
}
return resultado;
});
return task;
}
|
Neste código estamos passando o CancellationToken para o método definindo o valor como default de forma a torná-lo não obrigatório.
A seguir estamos verificando se o método deve ser cancelado lendo a propriedade IsCancellationRequested.(Também é possível usar o método ThrowIfCancellationRequested, que lançará uma OperationCanceledException).
A seguir estamos lançando um exceção do tipo TaskCanceledException(), que que representa uma exceção usada para comunicar o cancelamento da tarefa, .e passando a tarefa.
A seguir podemos chamar o método passando o token de cancelamento com o valor Cancel e estamos usando um bloco try/cacth para tratar a exceção TaskCanceledException:
class Program { private static CancellationTokenSource cancellationTokenSource; static async Task Main(string[] args) { try { await ExecutaTaskCancelamentoAsync(); } catch (TaskCanceledException ex) { Console.WriteLine(ex.Message); } Console.ReadKey(); } public static async Task ExecutaTaskCancelamentoAsync()
{
var stopwatch = new Stopwatch();
stopwatch.Start();
cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource?.Cancel();
Console.WriteLine($"Executou : {nameof(ExecutaTaskAsync)}"); Console.WriteLine("Resultado {0}", await OperacaoLongaDuracaoCancelavel(100, cancellationTokenSource.Token)); Console.WriteLine("Tempo gasto..." + stopwatch.Elapsed + "\n"); } ... }
|
No método Main estamos usando um bloco try/catch para tratar a exceção que será lançada quando a tarefa for cancelada e chamando o método ExecutaTaskCancelamentoAsync.
Neste método estou criando uma instância de CancellationTokenSource e passando o token com a notificação de cancelamento. Isso vai cancelar a operação de imediato.
A seguir temos o resultado obtido:
Na próxima parte do artigo veremos como cancelar uma operação assíncrona em um período de tempo específico.
E estamos conversados...
Não se turbe o
vosso coração; credes em Deus, crede também em mim.
Na casa de meu Pai há muitas moradas. Se assim não fora, eu vo-lo teria dito.
Pois vou preparar-vos lugar.
João 14:1,2
Referências: