CancellationToken – Użycie na przykładzie WebApi

Udostępnij

Dzisiejszy post będzie dotyczył wykorzystania CancellationToken w C#. Uważam, że najłatwiej zrozumieć jego działanie na przykładzie zapytań realizowanych za pomocą Web API.

Czym jest CancellationToken? To mechanizm, który umożliwia kontrolowanie przerwania (długo trwających) operacji. Działa podobnie do sygnału, który informuje kod o konieczności anulowania wykonywanego żądania. Web API ma wbudowane wsparcie dla CancellationToken. Najprostszym sposobem jego użycia jest przekazanie go jako parametru do akcji kontrolera. Dzięki temu możemy monitorować, czy klient zakończył połączenie (np. przez zamknięcie przeglądarki, anulowanie zapytania itp.).

Dlaczego warto korzystać z CancellationToken?

Optymalizacja zasobów serwera

Dzięki anulowaniu operacji, na których wynik nikt nie czeka, możemy zwolnić zasoby, takie jak połączenie do bazy danych, wątki czy inne zależności. Wiele aplikacji jest obecnie hostowanych w chmurze, gdzie długotrwałe (choć nie tylko) operacje mogą generować wysokie koszty. Powinniśmy dążyć do optymalnego zużycia zasobów.

User Experience

Aplikacja serwerowa powinna odpowiednio reagować na zdarzenia – jeśli klient zdecyduje się przerwać procesowanie, serwer również powinien to zrobić.

Czym jest CancellationToken w c#

CancellationToken jest strukturą, co oznacza, że jest typem wartościowym, czyli przekazywany przez kopię. Ze względu na to, że często jest przekazywany między metodami, może to mieć pozytywny wpływ na wydajność.

Jak wspomniałem wcześniej, WebAPI wspiera używanie CancellationToken, co pozwala na łatwe dodanie go do akcji kontrolera. Następnie, mając dostęp do CancellationToken, możemy go propagować do metod, których używamy w naszym kodzie i które przyjmują CancellationToken jako parametr. Aby lepiej zobrazować, o co chodzi, posłużmy się metodą Task.Delay – 10 sekund powinno wystarczyć na nasz test. Pamiętajmy o przekazaniu CancellationToken. Dla lepszego zobrazowania wyników warto uruchomić aplikację w trybie Debug, z włączonymi wszystkimi wyjątkami.

[HttpGet]
public async Task<IActionResult> GetData(CancellationToken cancellationToken)
{
    await Task.Delay(10000, cancellationToken);
    return Ok(Guid.NewGuid());
}

Próbowałem uruchomić powyższy kawałek, wysyłając request przez Swaggera, korzystając z nowego mechanizmu w Visual Studio przy użyciu plików .http oraz z przeglądarki. Wyniki są dosyć interesujące – okazuje się, że Swagger dostarcza przycisk „Cancel”, ale niestety nie anuluje on requesta. Wizualnie widać, że przycisk został wciśnięty, ale zapytanie nadal jest wykonywane. Podobnie wygląda sytuacja przy użyciu plików .http. Jedynie przeglądarka internetowa nie zawiodła. Jeśli w ciągu 10 sekund próbujemy anulować request, otrzymamy oczekiwany wyjątek, czyli OperationCanceledException.

Kolejne kroki

Co dalej? Z jednej strony eksperyment się udał – jesteśmy w stanie anulować operacje w trakcie procesowania, ale pojawił się inny „problem”. Nasza aplikacja zaczęła rzucać wyjątek, co jest zgodne z oczekiwaniami, ale musimy przygotować nasz kod na tę sytuację. Najprostszym rozwiązaniem tego problemu jest dodanie middleware, które obsłuży wszystkie wyjątki typu OperationCanceledException.

public class OperationCanceledMiddleware
{
    private readonly RequestDelegate _next;

    public OperationCanceledMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (OperationCanceledException)
        {
            // właściwa obsługa dla wyjątku 
            context.Response.StatusCode = 499; // w formie ciekawostki - Client Closed Request
        }
    }
}

app.UseMiddleware<OperationCanceledMiddleware>();

Czytaj również  Fody + INotifyPropertyChanged
Scroll to Top