ASP.NET Core Web API -  Usando CancellationToken


 Hoje veremos como usar o CancellationToken em um projeto ASP.NET Core Web API.

Em uma aplicação web requisições abortadas ou orfãs são muito comuns. Isso pode ocorrer quando o usuário é desconectado por interrupção da comunicação na rede ou pela navegação entre várias páginas antes de obter a resposta ou pelo fechamento do navegador.


Uma requisição órfã não pode entregar uma resposta ao cliente, mas vai executar todas as etapas (como chamadas de banco de dados, chamadas HTTP etc.) no servidor, e, neste caso seria melhor encerrar a execução imediatamente para evitar isso.

Aqui é que entra o CancellationToken que pode ser usado para encerrar a execução de uma requisição no servidor imediatamente assim que a solicitação for anulada ou se tornar órfã. Nestes cenários um CancellationToken permite o cancelamento cooperativo entre threads ou objetos Task e é usado em tarefas assíncronas para sinalizar que a tarefa deve ser cancelada.

Para mostrar o uso deste recurso vamos criar um projeto Web API chamado ApiCancellationToken usando o template ASP .NET Core Web API no VS 2022

Vamos criar o projeto sem habilitar a Open API e vamos alterar o código do controlador WeatherForecast no método Action Get conforme abaixo:

        [HttpGet()]
        public async Task<IEnumerable<WeatherForecast>> Get()
        {
            System.Diagnostics.Debug.WriteLine("Requisição para WeatherForecast...");           

            await Task.Delay(5000);

            var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();

            System.Diagnostics.Debug.WriteLine($"Número de resultados : {result.Length}");
            return result;
        }

No endpoint Get temos o retorno da previsão do tempo gerados aleatoriamente. Em um cenário do mundo real, esses dados podem estar armazenados em um banco de dados SQL ou retornados de outra solicitação HTTP que consome uma outra API.

Para testar definimos mensagens usando o System.Diagnostics.Debug.WriteLine  para que possamos ver o que está acontecendo na janela Output e também foi incluído um atraso de 5 segundos na execução para imitar uma chamada a um banco de dados.

Executando e testando o projeto temos que na chamada do endpoint Get usando o Postman iremos obter o seguinte resultado:

E na janela Output teremos a exibição as mensagens indicando que a requisição foi processada e o resultado obtido:

Agora repetir o teste e simular requisições órfãs. Podemos fazer isso enviando e cancelando a requisição de imediato ou fechando o navegador:

Para testar enviamos um request e em seguida cancelemos a requisição duas vezes, e, abaixo temos o resultado obtido na janela Ouput:

Como podemos constatar mesmo cancelando a requisição ela foi executada no servidor.

Como podemos resolver este problema ?

Para resolver esse problema e proteger nosso banco de dados e recursos, podemos usar um CancellationToken

Vamos então alterar o código do endpoint Get incluindo o código abaixo:

        [HttpGet]
        public async Task<IEnumerable<WeatherForecast>> Get(CancellationToken ct)
        {
            try
            {

                System.Diagnostics.Debug.WriteLine("##### Requisição para WeatherForecast... #####");

                await Task.Delay(5000, ct);

                var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
                {
                    Date = DateTime.Now.AddDays(index),
                    TemperatureC = Random.Shared.Next(-20, 55),
                    Summary = Summaries[Random.Shared.Next(Summaries.Length)]
                })
                .ToArray();

                System.Diagnostics.Debug.WriteLine($"###### Número de resultados : {result.Length}");
                return result;
            }
            catch (TaskCanceledException cte)
            {
                throw;
            }

        }

Neste código incluímos no endpoint Get o parâmetro ct do tipo CancellationToken e , dentro de um bloco try/catch,  em Task.Delay passamos este parâmetro.

A seguir no catch definimos para capturar a exceção do tipo TaskCanceledException que representa uma exceção usada para comunicar o cancelamento da tarefa.

Repetindo o teste onde enviamos um request e em seguida cancelamos o request agora teremos o seguinte resultado:

Nesta abordagem, a aplicação vai continuar processando novas solicitações, no entanto, se uma solicitação for cancelada, ela lançará uma TaskCanceledException.

Com isso temos as seguintes vantagens:

Dessa forma, fornecer um token de cancelamento é uma boa prática, pois economiza recursos e tempo.

Neste artigo eu apenas apresentei o problema e uma forma bem simples de contorná-lo; voltaremos em breve a este assunto com mais detalhes.

E estamos conversados.

"Esta é a vida eterna: que te conheçam, o único Deus verdadeiro, e a Jesus Cristo, a quem enviaste"
João 17:3

Referências:


José Carlos Macoratti