ASP.NET Web API OperationCanceledException, wenn der Browser die Anforderung abbricht

119

Wenn ein Benutzer eine Seite lädt, stellt er eine oder mehrere Ajax-Anforderungen, die auf ASP.NET Web API 2-Controller treffen. Wenn der Benutzer zu einer anderen Seite navigiert, bevor diese Ajax-Anforderungen abgeschlossen sind, werden die Anforderungen vom Browser abgebrochen. Unser ELMAH HttpModule protokolliert dann zwei Fehler für jede stornierte Anfrage:

Fehler 1:

System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__0.MoveNext()

Fehler 2:

System.OperationCanceledException: The operation was canceled.
   at System.Threading.CancellationToken.ThrowIfCancellationRequested()
   at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.WebHost.HttpControllerHandler.<CopyResponseAsync>d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.WebHost.HttpControllerHandler.<ProcessRequestAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.TaskAsyncHelper.EndTask(IAsyncResult ar)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Wenn ich mir den Stacktrace ansehe, sehe ich, dass die Ausnahme von hier ausgelöst wird: https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http.WebHost/HttpControllerHandler.cs# L413

Meine Frage ist: Wie kann ich mit diesen Ausnahmen umgehen und sie ignorieren?

Es scheint außerhalb des Benutzercodes zu liegen ...

Anmerkungen:

  • Ich verwende die ASP.NET-Web-API 2
  • Die Web-API-Endpunkte sind eine Mischung aus asynchronen und nicht asynchronen Methoden.
  • Unabhängig davon, wo ich die Fehlerprotokollierung hinzufüge, kann ich die Ausnahme im Benutzercode nicht abfangen
Bates Westmoreland
quelle
1
Wir haben die gleichen Ausnahmen (TaskCanceledException und OperationCanceledException) auch bei der aktuellen Version der Katana-Bibliotheken gesehen.
David McClelland
Ich habe einige weitere Details darüber gefunden, wann beide Ausnahmen auftreten, und herausgefunden, dass diese Problemumgehung nur für eine von ihnen funktioniert. Hier sind einige Details: stackoverflow.com/questions/22157596/…
Ilya Chernomordik

Antworten:

78

Dies ist ein Fehler in ASP.NET Web API 2, und ich glaube leider nicht, dass es eine Problemumgehung gibt, die immer erfolgreich sein wird. Wir haben einen Fehler gemeldet , um ihn auf unserer Seite zu beheben.

Letztendlich besteht das Problem darin, dass wir in diesem Fall eine abgebrochene Aufgabe an ASP.NET zurückgeben und ASP.NET eine abgebrochene Aufgabe wie eine nicht behandelte Ausnahme behandelt (das Problem wird im Anwendungsereignisprotokoll protokolliert).

In der Zwischenzeit können Sie so etwas wie den folgenden Code ausprobieren. Es wird ein Nachrichtenhandler der obersten Ebene hinzugefügt, der den Inhalt entfernt, wenn das Stornierungs-Token ausgelöst wird. Wenn die Antwort keinen Inhalt hat, sollte der Fehler nicht ausgelöst werden. Es gibt immer noch eine kleine Möglichkeit, dass dies passiert, da der Client die Verbindung direkt trennen kann, nachdem der Nachrichtenhandler das Stornierungs-Token überprüft hat, aber bevor der übergeordnete Web-API-Code dieselbe Überprüfung durchführt. Aber ich denke, es wird in den meisten Fällen helfen.

David

config.MessageHandlers.Add(new CancelledTaskBugWorkaroundMessageHandler());

class CancelledTaskBugWorkaroundMessageHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

        // Try to suppress response content when the cancellation token has fired; ASP.NET will log to the Application event log if there's content in this case.
        if (cancellationToken.IsCancellationRequested)
        {
            return new HttpResponseMessage(HttpStatusCode.InternalServerError);
        }

        return response;
    }
}
dmatson
quelle
2
Als Update erfasst dies einige der Anforderungen. Wir sehen immer noch einige in unseren Protokollen. Danke für die Umgehung. Ich freue mich auf eine Lösung.
Bates Westmoreland
2
@KiranChalla - Ich kann bestätigen, dass das Upgrade auf 5.2.2 immer noch diese Fehler aufweist.
KnightFox
2
Trotzdem bekomme ich den Fehler. Ich habe oben Vorschlag andere Hinweise verwendet.
M2012
3
Als ich die obige Empfehlung ausprobiert habe, habe ich immer noch Ausnahmen erhalten, als die Anfrage abgebrochen wurde, noch bevor sie weitergeleitet wurde SendAsync(Sie können dies simulieren, indem Sie F5im Browser eine URL gedrückt halten, die Anfragen an Ihre API stellt. Ich habe dieses Problem auch gelöst Hinzufügen des if (cancellationToken.IsCancellationRequested)Schecks über dem Aufruf von SendAsync. Jetzt werden die Ausnahmen nicht mehr angezeigt, wenn der Browser Anfragen schnell storniert.
Seangwright
2
Ich habe einige weitere Details darüber gefunden, wann beide Ausnahmen auftreten, und herausgefunden, dass diese Problemumgehung nur für eine von ihnen funktioniert. Hier sind einige Details: stackoverflow.com/a/51514604/1671558
Ilya Chernomordik
17

Bei der Implementierung eines Ausnahmeprotokolls für WebApi wird empfohlen, die System.Web.Http.ExceptionHandling.ExceptionLoggerKlasse zu erweitern , anstatt einen ExceptionFilter zu erstellen. Die WebApi-Interna rufen die Protokollmethode von ExceptionLoggers für abgebrochene Anforderungen nicht auf (Ausnahmefilter erhalten sie jedoch). Dies ist beabsichtigt.

HttpConfiguration.Services.Add(typeof(IExceptionLogger), myWebApiExceptionLogger); 
Shaddy Zeineddine
quelle
Es scheint, dass das Problem bei diesem Ansatz darin besteht, dass der Fehler immer noch in der Global.asax-Fehlerbehandlung auftritt ... Ereignis, obwohl es nicht an den Ausnahmebehandler gesendet wird
Ilya Chernomordik
14

Hier ist eine weitere Problemumgehung für dieses Problem. Fügen Sie einfach am Anfang der OWIN-Pipeline eine benutzerdefinierte OWIN-Middleware hinzu, die Folgendes abfängt OperationCanceledException:

#if !DEBUG
app.Use(async (ctx, next) =>
{
    try
    {
        await next();
    }
    catch (OperationCanceledException)
    {
    }
});
#endif
huysentruitw
quelle
2
Ich habe diesen Fehler hauptsächlich aus dem OWIN-Kontext erhalten und dieser zielt besser darauf ab
NitinSingh
3

Sie können versuchen, das Standardverhalten bei der Behandlung von TPL-Aufgabenausnahmen zu ändern, indem Sie web.config:

<configuration> 
    <runtime> 
        <ThrowUnobservedTaskExceptions enabled="true"/> 
    </runtime> 
</configuration>

Dann haben Sie eine staticKlasse (mit einem staticKonstruktor) in Ihrer Web-App, die damit umgehen würde AppDomain.UnhandledException.

Es scheint jedoch, dass diese Ausnahme tatsächlich irgendwo in der ASP.NET-Web-API-Laufzeit behandelt wird , bevor Sie überhaupt die Möglichkeit haben, sie mit Ihrem Code zu behandeln.

In diesem Fall sollten Sie in der Lage sein, es als Ausnahme der ersten Chance abzufangen AppDomain.CurrentDomain.FirstChanceException. Hier erfahren Sie , wie . Ich verstehe, dass dies möglicherweise nicht das ist, wonach Sie suchen.

noseratio
quelle
1
Keiner von beiden erlaubte mir, die Ausnahme zu behandeln.
Bates Westmoreland
@ BatesWestmoreland, nicht einmal FirstChanceException? Haben Sie versucht, es mit einer statischen Klasse zu behandeln, die über HTTP-Anforderungen hinweg bestehen bleibt?
Noseratio
2
Das Problem, das ich zu lösen versuche, um diese Ausnahmen zu erkennen und zu ignorieren. Mit AppDomain.UnhandledExceptionoder AppDomain.CurrentDomain.FirstChanceExceptionkann ich die Ausnahme überprüfen, aber nicht abfangen und ignorieren. Ich habe keine Möglichkeit gesehen, diese Ausnahmen als mit einem dieser Ansätze behandelt zu markieren. Korrigieren Sie mich, wenn ich falsch liege.
Bates Westmoreland
2

Ich bekomme manchmal die gleichen 2 Ausnahmen in meiner Web API 2-Anwendung, aber ich kann sie mit der Application_ErrorMethode in Global.asax.csund unter Verwendung eines generischen Ausnahmefilters abfangen .

Das Lustige ist jedoch, dass ich diese Ausnahmen lieber nicht abfange, weil ich immer alle nicht behandelten Ausnahmen protokolliere, die die Anwendung zum Absturz bringen können (diese beiden sind jedoch für mich irrelevant und scheinen anscheinend nicht oder zumindest nicht zum Absturz zu kommen es, aber ich kann mich irren). Ich vermute, dass diese Fehler aufgrund eines Ablaufs der Zeitüberschreitung oder einer expliziten Stornierung durch den Client auftreten, aber ich hätte erwartet, dass sie innerhalb des ASP.NET-Frameworks behandelt und nicht als nicht behandelte Ausnahmen außerhalb des ASP.NET-Frameworks weitergegeben werden.

Gabriel S.
quelle
In meinem Fall treten diese Ausnahmen auf, weil der Browser die Anforderung abbricht, wenn der Benutzer zu einer neuen URL navigiert.
Bates Westmoreland
1
Aha. In meinem Fall werden die Anforderungen über die WinHTTPAPI und nicht über einen Browser ausgegeben .
Gabriel S.
2

Ich habe ein bisschen mehr Details zu diesem Fehler gefunden. Es gibt zwei mögliche Ausnahmen, die auftreten können:

  1. OperationCanceledException
  2. TaskCanceledException

Der erste Fall tritt auf, wenn die Verbindung unterbrochen wird, während Ihr Code im Controller ausgeführt wird (oder möglicherweise auch ein Systemcode). Während die zweite passiert, wenn die Verbindung getrennt wird, während sich die Ausführung innerhalb eines Attributs befindet (z AuthorizeAttribute. B. ).

Die bereitgestellte Problemumgehung hilft also, die erste Ausnahme teilweise zu mildern, und hilft nicht bei der zweiten. Im letzteren Fall TaskCanceledExceptiontritt das während des base.SendAsyncAufrufs selbst auf, anstatt dass das Stornierungs-Token auf true gesetzt wird.

Ich kann zwei Möglichkeiten sehen, diese zu lösen:

  1. Ignorieren Sie einfach beide Ausnahmen in global.asax. Dann stellt sich die Frage, ob es möglich ist, stattdessen plötzlich etwas Wichtiges zu ignorieren.
  2. Wenn Sie einen zusätzlichen Versuch / Fang im Handler ausführen (obwohl er nicht kugelsicher ist +, besteht immer noch die Möglichkeit, TaskCanceledExceptiondass wir ihn ignorieren und ihn protokollieren möchten.

config.MessageHandlers.Add(new CancelledTaskBugWorkaroundMessageHandler());

class CancelledTaskBugWorkaroundMessageHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        try
        {
            HttpResponseMessage response = await base.SendAsync(request, cancellationToken);

            // Try to suppress response content when the cancellation token has fired; ASP.NET will log to the Application event log if there's content in this case.
            if (cancellationToken.IsCancellationRequested)
            {
                return new HttpResponseMessage(HttpStatusCode.InternalServerError);
            }
        }
        catch (TaskCancellationException)
        {
            // Ignore
        }

        return response;
    }
}

Der einzige Weg, wie ich herausgefunden habe, dass wir versuchen können, die falschen Ausnahmen zu lokalisieren, besteht darin, zu überprüfen, ob Stacktrace Asp.Net-Material enthält. Scheint allerdings nicht sehr robust zu sein.

PS So filtere ich diese Fehler heraus:

private static bool IsAspNetBugException(Exception exception)
{
    return
        (exception is TaskCanceledException || exception is OperationCanceledException) 
        &&
        exception.StackTrace.Contains("System.Web.HttpApplication.ExecuteStep");
}
Ilya Chernomordik
quelle
1
In Ihrem vorgeschlagenen Code erstellen Sie die responseVariable innerhalb des Versuchs und geben sie außerhalb des Versuchs zurück. Das kann doch nicht funktionieren, oder? Auch wo verwenden Sie die IsAspNetBugException?
Schoof
1
Nein, das kann nicht funktionieren, es muss natürlich nur außerhalb des Try / Catch-Blocks deklariert und mit so etwas wie einer abgeschlossenen Aufgabe initialisiert werden. Es ist nur ein Beispiel für die Lösung, die sowieso nicht kugelsicher ist. Die andere Frage wird im Global.Asax OnError-Handler verwendet. Wenn Sie diese nicht zum Protokollieren Ihrer Nachrichten verwenden, müssen Sie sich trotzdem keine Sorgen machen. Wenn Sie dies tun, ist dies ein Beispiel dafür, wie Sie "Nicht-Fehler" aus dem System herausfiltern.
Ilya Chernomordik
1

Wir haben die gleiche Ausnahme erhalten. Wir haben versucht, die Problemumgehung von @ dmatson zu verwenden, aber wir haben immer noch eine Ausnahme erhalten. Wir haben uns bis vor kurzem damit befasst. Wir haben festgestellt, dass einige Windows-Protokolle mit alarmierender Geschwindigkeit wachsen.

Fehlerdateien in: C: \ Windows \ System32 \ LogFiles \ HTTPERR

Die meisten Fehler betrafen "Timer_ConnectionIdle". Ich suchte herum und es schien, dass die Verbindung trotz Beendigung des Web-API-Aufrufs noch zwei Minuten nach der ursprünglichen Verbindung bestehen blieb.

Ich dachte mir dann, wir sollten versuchen, die Verbindung in der Antwort zu schließen und zu sehen, was passiert.

Ich response.Headers.ConnectionClose = true;habe dem SendAsync MessageHandler hinzugefügt , und soweit ich weiß, schließen die Clients die Verbindungen, und das Problem tritt nicht mehr auf.

Ich weiß, dass dies nicht die beste Lösung ist, aber es funktioniert in unserem Fall. Ich bin mir auch ziemlich sicher, dass Sie dies in Bezug auf die Leistung nicht tun möchten, wenn Ihre API mehrere Aufrufe von demselben Client hintereinander erhält.

Michael Margala
quelle