Auf diese Antwort wartet niemand mehr
Jeder abgebrochene HTTP-Request ohne Cancellation arbeitet für einen Client weiter, der längst weg ist: Die Action rechnet weiter, die Datenbank kaut an einer teuren Query, und der Connection Pool hält eine Verbindung besetzt. Unter Last macht genau dieses Muster aus einem Schluckauf einen Ausfall, denn ungeduldige Benutzer, die die Seite neu laden, verdoppeln die Arbeit, statt sie abzubrechen.
.NET hat darauf eine erstklassige Antwort: den CancellationToken. ASP.NET Core erstellt ihn bei jedem Request automatisch. Der eigene Code muss ihn nur annehmen und weiterreichen, und zwar jedes Mal.
Ein Abbruch wandert durch alle Schichten
Die komplette Kette funktioniert ohne eigenes Plumbing. Jede Schicht muss den Token nur an die nächste weitergeben:
Ausprobieren: einen Request mitten im Flug abbrechen
Starten Sie einen simulierten Request, dessen SQL-Query einige Sekunden dauert, und schliessen Sie den Tab, bevor sie fertig ist. Wechseln Sie den Modus, um zu sehen, was sich ohne weitergereichten CancellationToken ändert.
- Browserwartet
- ASP.NET Core (Kestrel)wartet
- Controller Actionwartet
- EF Core + SqlClientwartet
- SQL Serverwartet
Tipp: Die Query dauert rund neun Sekunden. Brechen Sie auf halbem Weg ab und beobachten Sie, wie weit die Cancellation kommt.
1. Der Browser bricht ab
Der Benutzer schliesst den Tab, navigiert weiter oder der Client läuft in einen Timeout. Auf der Leitung heisst das schlicht: Die Verbindung wird geschlossen.
2. ASP.NET Core merkt es
Kestrel erkennt die geschlossene Verbindung und cancelt HttpContext.RequestAborted. Deklariert die Action einen CancellationToken-Parameter, liefert das Model Binding genau diesen Token.
3. EF Core und SqlClient reichen weiter
ToListAsync(cancellationToken) und Co. geben den Token durch EF Core an Microsoft.Data.SqlClient weiter, und dort passiert der eigentliche Trick.
4. SQL Server stoppt die Query
SqlClient schickt ein Attention-Signal an den SQL Server, der die womöglich teure Query abbricht und die Ressourcen freigibt. Die Action endet mit einer OperationCanceledException, die ASP.NET Core als das behandelt, was sie ist: kein Ereignis.
[HttpGet("reports/{year:int}")]
public async Task<IActionResult> GetYearlyReport(
int year,
CancellationToken cancellationToken)
{
// Model binding fills this token from HttpContext.RequestAborted.
// Passing it on means SQL Server receives an attention signal and
// stops the query the moment the client disconnects.
var report = await dbContext.Orders
.Where(o => o.CreatedAt.Year == year)
.GroupBy(o => o.CustomerId)
.Select(g => new { CustomerId = g.Key, Total = g.Sum(o => o.Total) })
.ToListAsync(cancellationToken);
return Ok(report);
}Architekturtests statt Code-Review-Disziplin
Das Muster funktioniert nur, wenn jede Action mitmacht, und genau das geht in Code Reviews an einem vollen Freitag unter. Ein Architekturtest macht aus der Konvention einen fehlschlagenden Build: Mit NetArchTest.Rules (pure Reflection geht genauso) sammeln wir alle Controller-Actions ein und prüfen, dass sie einen CancellationToken akzeptieren.
Der Test nennt jede fehlbare Action in seiner Fehlermeldung, die Korrektur ist damit eine Sache von Sekunden. Analyzer wie CA2016 ergänzen den Test und warnen, wenn ein Token zwar existiert, aber nicht weitergereicht wird.
[Fact]
public void Controller_actions_accept_a_CancellationToken()
{
var controllers = Types.InAssembly(typeof(OrdersController).Assembly)
.That()
.HaveNameEndingWith("Controller")
.And().Inherit(typeof(ControllerBase))
.GetTypes();
var offenders = controllers
.SelectMany(c => c.GetMethods(BindingFlags.Public | BindingFlags.Instance))
.Where(m => m.GetCustomAttributes<HttpMethodAttribute>().Any())
.Where(m => m.GetParameters()
.All(p => p.ParameterType != typeof(CancellationToken)))
.Select(m => $"{m.DeclaringType!.Name}.{m.Name}")
.ToList();
offenders.ShouldBeEmpty(
$"These actions do not accept a CancellationToken: " +
$"{string.Join(", ", offenders)}");
}Das Wichtigste
Den Token in jeder Action annehmen, an jeden async Call weiterreichen und die Konvention von einem Architekturtest bewachen lassen. Das Ergebnis: Abgebrochene Requests verbrauchen weder Server noch Datenbank noch Geduld.
Weitere Leistungen rund um .NET
Kontakt
Lassen Sie uns gemeinsam etwas Grossartiges aufbauen
Haben Sie eine Frage oder möchten Sie ein Projekt besprechen? Wir freuen uns, von Ihnen zu hören. Füllen Sie das untenstehende Formular aus und wir melden uns so schnell wie möglich bei Ihnen.
Sie können uns auch direkt erreichen unter [email protected]