DEV Community

Marcos Belorio
Marcos Belorio

Posted on • Updated on

Cancelando Tasks assíncronas em C#

Uma das boas práticas ao trabalhar com Tasks assíncronas em C# é utilizar o CancellationToken, possibilitando que a execução pare quando solicitada.

Se você ainda não está familiarizado com o uso de Tasks Assíncronas e as palavras chaves async e await, recomendo ler este outro post antes:
Trabalhando com Tasks assíncronas em C#

Executando requisições sem a possibilidade de cancelamento

Vamos criar um exemplo mostrando como é a execução de uma requisição sem a possibilidade de cancelamento, para isso iremos criar o seguinte endpoint em uma aplicação web api:

[HttpGet]
public async Task<IActionResult> Get()
{
    Console.WriteLine("Iniciando");

    //simulando um processamento de 10 segundos
    await Task.Delay(10000);

    Console.WriteLine("Finalizando após 10 segundos");

    return Ok("Finalizado");
}
Enter fullscreen mode Exit fullscreen mode

Executando no navegador esse endpoint e aguardando o retorno com a mensagem "Finalizado", podemos ver o console da aplicação desse jeito:

Iniciando
Finalizando após 10 segundos
Enter fullscreen mode Exit fullscreen mode

Agora execute novamente no navegador o endpoint, mas antes da requisição terminar aperte f5 (atualizar) duas vezes, o console irá ficar assim:

Iniciando
Iniciando
Iniciando
Finalizando após 10 segundos
Finalizando após 10 segundos
Finalizando após 10 segundos
Enter fullscreen mode Exit fullscreen mode

Concluímos que mesmo atualizando a página no navegador, todas as requisições foram totalmente processadas, mesmo as que não tiveram tempo de retornar no navegador o response por ter sido atualizado antes. Esse comportamento pode ser esperado caso a requisição efetue alguma persistência em banco por exemplo, mas também pode ser um desperdício de processamento caso a requisição não mude nenhum estado da aplicação.

Usando o CancellationToken

CancellationToken é um objeto leve capaz de notificar caso a requisição tenha sido cancelada. Passar um CancellationToken como parâmetro para um método que retorne uma Task é uma boa prática, dando a possibilidade para o chamador do método desistir do resultado.

Vamos refazer o exemplo acima utilizando o CancellationToken:

[HttpGet]
public async Task<IActionResult> Get(CancellationToken ct)
{
    Console.WriteLine("Iniciando");

    //simulando um processamento de 10 segundos
    await Task.Delay(10000, ct); 

    Console.WriteLine("Finalizando após 10 segundos");

    return Ok("Finalizado");
}
Enter fullscreen mode Exit fullscreen mode

Note que foi passado um CancellationToken como parâmetro para o método Task.Delay(). Agora execute novamente no navegador o endpoint, e atualize a página antes da requisição terminar, o console irá ficar assim:

Iniciando
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
System.Threading.Tasks.TaskCanceledException: A task was canceled.    
//toda stack do erro
Iniciando
Finalizando após 10 segundos
Enter fullscreen mode Exit fullscreen mode

Diferente do outro exemplo onde a primeira requisição foi totalmente processada, dessa vez ela retornou uma exceção avisando que a Task foi cancelada e não chegou a ser totalmente processada.

Caso seja necessário você pode tratar essa exceção, como por exemplo retornar um erro amigável, através das exceptions TaskCanceledException ou OperationCanceledException.

Manipulando CancellationToken em seu código

Ao criar um método que retorne uma Task, você pode verificar o CancellationToken para impedir que a execução do código continue. Podemos fazer assim:

private async Task DoSomething(CancellationToken token)
{
    token.ThrowIfCancellationRequested();

    //Algum comportamento aqui
}
Enter fullscreen mode Exit fullscreen mode

No exemplo acima o método retorna uma exceção caso a requisição tenha sido cancelada.
Você também pode criar seu próprio comportamento quando a requisição for cancelada, ao invés de retornar uma exceção, veja o exemplo abaixo:

if (token.IsCancellationRequested)
{
    //Algum comportamento aqui
}
Enter fullscreen mode Exit fullscreen mode

Outra forma de se trabalhar com o CancellationToken é definir um timeout para ele ser cancelado, ou seja, se a execução passar do tempo definido no token, o token será cancelado. Vamos ver um exemplo:

[HttpGet]
public async Task<IActionResult> Get()
{
    Console.WriteLine("Iniciando");

    //Criando um CancellationTokenSource com timeout de 5 segundos
    CancellationTokenSource cts = new CancellationTokenSource(5000);
    CancellationToken ct = cts.Token;

    //simulando um processamento de 10 segundos
    await Task.Delay(10000, ct);

    Console.WriteLine("Finalizando após 10 segundos");

    return Ok("Finalizado");
}
Enter fullscreen mode Exit fullscreen mode

Podemos ver que criamos um CancellationToken onde será automaticamente cancelado após 5 segundos, como nosso código simula um processamento de 10 segundos, o método será cancelado antes de ser totalmente executado.

Por último, caso você precise utilizar um método que obrigue você passar um CancellationToken mas por algum motivo você espera que aquele código sempre seja executado independente se a requisição foi cancelada ou não, passe o token dessa forma:

await DoSomething(CancellationToken.None);
Enter fullscreen mode Exit fullscreen mode

Dessa forma você está passando um token vazio, então ele não terá nenhum comportamento.

Referências:
Using CancellationTokens in ASP.NET Core MVC controllers
Cancel asynchronous operations in C#

Latest comments (0)