Cancelando Tasks assíncronas em C#
Tags: csharp, dotnet, dotnetcore, task
Published: 2021-03-12 13:33:37

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#](https://dev.to/marcosbelorio/trabalhando-com-tasks-assincronas-em-c-pjp)


##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:

```csharp
[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");
}
```
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
```

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
```

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:

```csharp
[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");
}
```
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
```

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:

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

	//Algum comportamento aqui
}
```
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:

```csharp
if (token.IsCancellationRequested)
{
	//Algum comportamento aqui
}
```

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:

```csharp
[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");
}
```
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:
```csharp
await DoSomething(CancellationToken.None);
```
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](https://andrewlock.net/using-cancellationtokens-in-asp-net-core-mvc-controllers/)
[Cancel asynchronous operations in C#](https://johnthiriet.com/cancel-asynchronous-operation-in-csharp/)