Runtime: HttpClient löst TaskCanceledException bei Zeitüberschreitung aus

Erstellt am 25. Mai 2017  ·  119Kommentare  ·  Quelle: dotnet/runtime

HttpClient löst unter bestimmten Umständen eine TaskCanceledException bei Zeitüberschreitung aus. Dies geschieht bei uns, wenn der Server stark ausgelastet ist. Wir konnten das umgehen, indem wir das Standard-Timeout erhöht haben. Der folgende Thread im MSDN-Forum fasst das Wesentliche des aufgetretenen Problems zusammen: https://social.msdn.microsoft.com/Forums/en-US/d8d87789-0ac9-4294-84a0-91c9fa27e353/bug-in-httpclientgetasync-should-throw -webexception-not-taskcanceledexception?forum=netfxnetcom&prof=erforderlich

Danke

area-System.Net.Http enhancement

Hilfreichster Kommentar

@karelz fügt eine Notiz hinzu, da ein anderer Kunde von dieser irreführenden Ausnahme betroffen ist :)

Alle 119 Kommentare

@awithy welche Version von .NET Core verwenden Sie?

1.1

Unabhängig davon, ob das Design richtig ist oder nicht, werden OperationCanceledExceptions für Zeitüberschreitungen ausgelöst (und TaskCanceledException ist eine OperationCanceledException).
cc: @davidsch

Unser Team fand das unintuitiv, aber es scheint wie vorgesehen zu funktionieren. Leider haben wir, als wir darauf stießen, ein wenig Zeit damit verschwendet, eine Quelle für die Stornierung zu finden. Dieses Szenario anders handhaben zu müssen als das Abbrechen von Aufgaben, ist ziemlich hässlich (wir haben einen benutzerdefinierten Handler erstellt, um dies zu verwalten). Dies scheint auch eine Überbeanspruchung des Begriffs Stornierung zu sein.

Nochmals vielen Dank für deine schnelle Antwort.

Scheint beabsichtigt zu sein + es gab nur 2 Kundenbeschwerden in den letzten 6+ Monaten.
Schließen.

@karelz fügt eine Notiz hinzu, da ein anderer Kunde von dieser irreführenden Ausnahme betroffen ist :)

Danke für die Info, bitte stimme auch dem obersten Beitrag zu (Probleme können danach sortiert werden), danke!

Unabhängig davon, ob das Design richtig ist oder nicht, werden OperationCanceledExceptions für Zeitüberschreitungen ausgelöst

Dies ist ein schlechtes Design IMO. Es gibt keine Möglichkeit festzustellen, ob die Anfrage tatsächlich abgebrochen wurde (dh das an SendAsync übergebene Abbruch-Token wurde abgebrochen) oder ob die Zeitüberschreitung abgelaufen ist. Im letzteren Fall wäre es sinnvoll, es erneut zu versuchen, im ersten Fall nicht. Es gibt einen TimeoutException , der perfekt für diesen Fall wäre, warum nicht ihn verwenden?

Ja, das hat auch unser Team aus der Fassung gebracht. Es gibt einen ganzen Stack Overflow-Thread, der dem Versuch gewidmet ist, damit umzugehen: https://stackoverflow.com/questions/10547895/how-can-i-tell-when-httpclient-has-timed-out/32230327#32230327

Ich habe vor ein paar Tagen einen Workaround veröffentlicht: https://www.thomaslevesque.com/2018/02/25/better-timeout-handling-with-httpclient/

Super, danke.

Wiedereröffnung, da das Interesse jetzt größer ist - der StackOverflow -Thread hat jetzt 69 Upvotes.

cc @dotnet/ncl

Irgendwelche Neuigkeiten ? Kommt immer noch in dotnet Core 2.0 vor

Es steht auf unserer Liste der Top-Probleme für Post-2.1. Es wird jedoch nicht in 2.0/2.1 behoben.

Wir haben mehrere Möglichkeiten, was zu tun ist, wenn eine Zeitüberschreitung auftritt:

  1. Wirf TimeoutException anstelle von TaskCanceledException .

    • Pro: Zeitüberschreitung leicht von expliziter Abbruchaktion zur Laufzeit zu unterscheiden

    • Nachteil: Technische Breaking Change – neue Art von Ausnahme wird ausgelöst.

  2. Wirf TaskCanceledException mit innerer Ausnahme als TimeoutException

    • Pro: Timeout von expliziter Abbruchaktion zur Laufzeit unterscheidbar. Kompatibler Ausnahmetyp.

    • Offene Frage: Ist es in Ordnung, den ursprünglichen Stack TaskCanceledException wegzuwerfen und einen neuen zu werfen, während InnerException (als TimeoutException.InnerException ) erhalten bleiben? Oder sollten wir die ursprünglichen TaskCanceledException aufbewahren und einfach einpacken?



      • Entweder: neue TaskCanceledException -> neue TimeoutException -> ursprüngliche TaskCanceledException (mit ursprünglicher InnerException, die null sein kann)


      • Oder: neue TaskCanceledException -> neue TimeoutException -> ursprüngliche TaskCanceledException.InnerException (kann null sein)



  3. Wirf TaskCanceledException mit einer Nachricht, die Timeout als Grund nennt

    • Pro: Es ist möglich, Timeout von expliziter Abbruchaktion aus der Nachricht / den Protokollen zu unterscheiden

    • Con: Kann zur Laufzeit nicht unterschieden werden, es ist "debug only".

    • Offene Frage: Wie in [2] - sollten wir die ursprüngliche TaskCanceledException umschließen oder ersetzen (und sie stapeln)

Ich neige zu Option [2], wobei der ursprüngliche Stapel der ursprünglichen TaskCanceledException wird.
@stephentoub @davidsh irgendwelche Gedanken?

Übrigens: Die Änderung sollte ziemlich einfach in HttpClient.SendAsync sein, wo wir das Timeout einrichten:
https://github.com/dotnet/corefx/blob/30fb78875148665b98748ede3013641734d9bf5c/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L433 -L461

Die Ausnahme der obersten Ebene sollte immer eine HttpRequestException sein. Das ist das Standard-API-Modell für HttpClient.

Darin sollte "timeouts" als innere Ausnahme wahrscheinlich eine TimeoutException sein. Wenn wir dies mit den anderen Problemen kombinieren würden (über das Ändern von HttpRequestion, um eine neue Aufzählungseigenschaft ähnlich wie WebExceptionStatus aufzunehmen), müssten Entwickler nur diese Aufzählung überprüfen und müssten überhaupt keine inneren Ausnahmen untersuchen.

Dies ist im Grunde Option 1), behält aber weiterhin das aktuelle App-kompatible API-Modell bei, dass Ausnahmen der obersten Ebene von HttpClient-APIs immer HttpRequestException sind.

Hinweis: dass alle "Zeitüberschreitungen" beim direkten Lesen eines HTTP-Antwortstreams von Stream-APIs wie:

```c#
var client = new HttpClient();
Stream responseStream = warte auf client.GetStreamAsync(url);

int bytesRead = warte auf responseStream.ReadAsync (buffer, 0, buffer.Length);
```

sollte weiterhin System.IO.IOException usw. als Ausnahmen der obersten Ebene auslösen (wir haben diesbezüglich tatsächlich einige Fehler in SocketsHttpHandler).

@davidsh schlagen Sie vor, HttpRequestException auch in Fällen zu werfen, in denen eine explizite Benutzerstornierung vorliegt? Diese werfen heute TaskCanceledException und es scheint in Ordnung zu sein. Ich bin mir nicht sicher, ob wir etwas ändern wollen ...
Ich wäre in Ordnung, clientseitige Timeouts als HttpRequestException anstelle von TaskCanceledException zu machen.

@davidsh schlagen Sie vor, HttpRequestException auch in Fällen auszulösen, in denen eine explizite Benutzerstornierung vorliegt? Diese werfen heute TaskCanceledException und es scheint in Ordnung zu sein. Ich bin mir nicht sicher, ob es etwas ist, das wir ändern wollen …

Ich muss die verschiedenen Verhaltensweisen der Stacks einschließlich .NET Framework testen, um das genaue aktuelle Verhalten zu überprüfen, bevor ich diese Frage beantworten kann.

Außerdem lösen einige unserer Stacks in einigen Fällen HttpRequestException mit einer inneren Ausnahme von OperationCanceledException aus. TaskCanceledException ist ein abgeleiteter Typ von OperationCanceledException.

wenn es eine explizite Benutzerkündigung gibt

Wir brauchen auch eine klare Definition dessen, was „explizit“ ist. Ist das Festlegen der HttpClient.Timeout-Eigenschaft beispielsweise „explizit“? Ich denke, wir sagen nein. Was ist mit dem Aufrufen von .Cancel() für das CancellationTokenSource-Objekt (das sich auf das übergebene CancellationToken-Objekt auswirkt)? Ist das "explizit"? Wahrscheinlich fallen uns noch andere Beispiele ein.

.NET Framework löst auch TaskCanceledException aus, wenn Sie HttpClient.Timeout festlegen.

Wenn Sie nicht zwischen Kündigung vom Kunden und Kündigung von der Implementierung unterscheiden können, kreuzen Sie einfach beide an. Erstellen Sie vor der Verwendung des Client-Tokens ein abgeleitetes:
```c#
var clientToken = ....
var innerTokenSource = CancellationTokenSource.CreateLinkedTokenSource(clientToken);

If at some point you catch **OperationCancelledException**:
```c#
try
{
     //invocation with innerToken
}
catch(OperationCancelledException oce)
{
      if(clientToken.IsCancellationRequested)
          throw; //as is, it is client initiated and in higher priority
      if(innerToken.IsCancellationRequested)
           //wrap it in HttpException, TimeoutException, whatever, wrap it in another linked token until you process all cases.
}

Grundsätzlich filtern Sie alle möglichen Schichten vom Client-Token bis zum tiefsten, bis Sie das Token finden, das die Stornierung verursacht hat.

Irgendwelche Updates dazu, 2.2 oder 3.0 zu machen? @karelz @davidsh , cc @NickCraver . Ich wünschte, es würde nur Option 1) zum Auslösen einer TimeoutException oder alternativ einer neuen HttpTimeoutException, die von HttpRequestException abgeleitet wird, tun. Danke!

Irgendwelche Updates dazu, 2.2 oder 3.0 zu machen?

Zu spät für 2.2, es wurde letzte Woche veröffentlicht ;)

Zu spät für 2.2, es wurde letzte Woche veröffentlicht ;)

Argh, das passiert, wenn ich eine Pause mache ;)

Die einfachste Problemumgehung, die ich gefunden habe, ist, wenn auch ein wenig doof, das Verschachteln eines Catch-Handlers für OperationCanceledException und das anschließende Auswerten des Werts von IsCancellationRequested, um festzustellen, ob das Abbruchtoken das Ergebnis einer Zeitüberschreitung oder eines tatsächlichen Sitzungsabbruchs war. Natürlich würde sich die Logik, die den API-Aufruf durchführt, wahrscheinlich in einer Geschäftslogik- oder Serviceebene befinden, aber ich habe das Beispiel der Einfachheit halber konsolidiert:

````scharf
[HttpGet]
öffentliche asynchrone AufgabeIndex (AbbruchToken AbbruchToken)
{
Versuchen
{
// Dies würde normalerweise eher in einer Geschäftslogik- oder Serviceebene als im Controller selbst erfolgen, aber ich veranschauliche den Punkt hier der Einfachheit halber
Versuchen
{
//HttpClientFactory für HttpClient-Instanzen verwenden
var httpClient = _httpClientFactory.CreateClient();

        //Set an absurd timeout to illustrate the point
        httpClient.Timeout = new TimeSpan(0, 0, 0, 0, 1);

        //Perform call that requires special timeout logic                
        var httpResponse = await httpClient.GetAsync("https://someurl.com/api/long/running");

        //... (if GetAsync doesn't fail, handle the response as desired)
    }
    catch (OperationCanceledException innerOperationCanceled)
    {
        //If a canceled token exception occurs due to a timeout, "IsCancellationRequested" should be false
        if (cancellationToken.IsCancellationRequested)
        {
            //Bubble exception to global handler
            throw innerOperationCanceled;
        }
        else
        {
            //... (perform timeout logic here)
        }                    
    }                

}
catch (OperationCanceledException operationCanceledEx)
{
    _logger.LogWarning(operationCanceledEx, "Request was aborted by the end user.");
    return new StatusCodeResult(499);
}
catch (Exception ex)
{
    _logger.LogError(ex, "An unexepcted error occured.");
    return new StatusCodeResult(500);
}

}
````

Ich habe andere Artikel darüber gesehen, wie dieses Szenario eleganter gehandhabt werden kann, aber sie alle erfordern das Graben in der Pipeline usw. Mir ist klar, dass dieser Ansatz nicht perfekt ist, da ich hoffe, dass es in Zukunft eine bessere Unterstützung für tatsächliche Timeout-Ausnahmen geben wird Freisetzung.

Das Arbeiten mit dem HTTP-Client ist absolut miserabel. HttpClient muss gelöscht und von Grund auf neu gestartet werden.

Die einfachste Problemumgehung, die ich gefunden habe, ist, wenn auch ein wenig doof, das Verschachteln eines Catch-Handlers für OperationCanceledException und das anschließende Auswerten des Werts von IsCancellationRequested, um festzustellen, ob das Abbruchtoken das Ergebnis einer Zeitüberschreitung oder eines tatsächlichen Sitzungsabbruchs war. Natürlich würde sich die Logik, die den API-Aufruf durchführt, wahrscheinlich in einer Geschäftslogik- oder Serviceebene befinden, aber ich habe das Beispiel der Einfachheit halber konsolidiert:

[HttpGet]
public async Task<IActionResult> Index(CancellationToken cancellationToken)
{
  try
  {
      //This would normally be done in a business logic or service layer rather than in the controller itself but I'm illustrating the point here for simplicity sake
      try
      {
          //Using HttpClientFactory for HttpClient instances      
          var httpClient = _httpClientFactory.CreateClient();

          //Set an absurd timeout to illustrate the point
          httpClient.Timeout = new TimeSpan(0, 0, 0, 0, 1);

          //Perform call that requires special timeout logic                
          var httpResponse = await httpClient.GetAsync("https://someurl.com/api/long/running");

          //... (if GetAsync doesn't fail, handle the response as desired)
      }
      catch (OperationCanceledException innerOperationCanceled)
      {
          //If a canceled token exception occurs due to a timeout, "IsCancellationRequested" should be false
          if (cancellationToken.IsCancellationRequested)
          {
              //Bubble exception to global handler
              throw innerOperationCanceled;
          }
          else
          {
              //... (perform timeout logic here)
          }                    
      }                

  }
  catch (OperationCanceledException operationCanceledEx)
  {
      _logger.LogWarning(operationCanceledEx, "Request was aborted by the end user.");
      return new StatusCodeResult(499);
  }
  catch (Exception ex)
  {
      _logger.LogError(ex, "An unexepcted error occured.");
      return new StatusCodeResult(500);
  }
}

Ich habe andere Artikel darüber gesehen, wie dieses Szenario eleganter gehandhabt werden kann, aber sie alle erfordern das Graben in der Pipeline usw. Mir ist klar, dass dieser Ansatz nicht perfekt ist, da ich hoffe, dass es in Zukunft eine bessere Unterstützung für tatsächliche Timeout-Ausnahmen geben wird Freisetzung.

Es ist absolut verrückt, dass wir diesen grausamen Code schreiben müssen. HttpClient ist die undichteste Abstraktion, die jemals von Microsoft erstellt wurde

Ich stimme zu, dass es ziemlich kurzsichtig erscheint, keine spezifischeren Ausnahmeinformationen zu Zeitüberschreitungen aufzunehmen. Scheint eine ziemlich grundlegende Sache für Leute zu sein, die tatsächliche Zeitüberschreitungen anders handhaben möchten als abgebrochene Token-Ausnahmen.

Das Arbeiten mit dem HTTP-Client ist absolut miserabel. HttpClient muss gelöscht und von Grund auf neu gestartet werden.\

Das ist kein sehr hilfreiches oder konstruktives Feedback @dotnetchris . Kunden und MSFT-Mitarbeiter sind gleichermaßen hier, um zu versuchen, die Dinge zu verbessern, und deshalb hören Leute wie @karelz immer noch auf Kundenfeedback und versuchen, sich zu verbessern. Sie müssen sich nicht offen entwickeln, und verbrennen wir ihren guten Willen nicht mit Negativität, sonst könnten sie sich entscheiden, ihren Ball zu nehmen und nach Hause zu gehen, und das wäre schlimmer für die Gemeinschaft. Danke! :)

Warum nicht einfach einen HttpClient2 (vorläufiger Name) erstellen, der das erwartete Verhalten aufweist, und in der Dokumentation mitteilen, dass „HttpClient veraltet ist, bitte HttpClient2 verwenden“ und den Unterschied zwischen den beiden erklären.

Auf diese Weise werden keine Breaking Changes in das .NetFramework eingefügt.

HttpClient sollte nicht auf dem Auslösen von Ausnahmen basieren. Es ist nicht ungewöhnlich, dass ein Internethost nicht verfügbar ist. Angesichts der unendlichen Permutationen von Adressen wird eigentlich erwartet, dass der Ausnahmefall darin besteht, etwas zu geben, das funktioniert.

Ich habe sogar vor über 3 Jahren darüber gebloggt, wie schlecht die Verwendung von HttpClient ist. https://dotnetchris.wordpress.com/2015/12/11/working-with-httpclient-is-absurd/

Das ist eklatant schlechtes Design.

Hat jemand in Microsoft sogar HttpClient verwendet? Es scheint sicher nicht so zu sein.

Dann gibt es jenseits dieser schrecklichen API-Oberfläche alle möglichen undichten Abstraktionen in Bezug auf deren Konstruktion und Threading. Ich kann nicht glauben, dass es möglich ist, gerettet zu werden, und nur brandneu ist eine praktikable Lösung. HttpClient2, HttpClientSlim ist in Ordnung oder was auch immer.

Der HTTP-Zugriff sollte niemals Ausnahmen auslösen. Es sollte immer ein Ergebnis zurückgeben, das Ergebnis sollte auf Vollständigkeit, Zeitüberschreitung, http-Antwort überprüft werden können. Der einzige Grund, warum es erlaubt sein sollte, zu werfen, ist, wenn Sie EnsureSuccess() aufrufen, dann ist es in Ordnung, jede Ausnahme in der Welt zu werfen. Aber es sollte keine Kontrollflussbehandlung für ein völlig normales Verhalten des Internets geben: Host aktiv, Host nicht aktiv, Host-Timeout. Keines davon ist außergewöhnlich.

HttpClient sollte nicht auf dem Auslösen von Ausnahmen basieren. Es ist nicht ungewöhnlich, dass ein Internethost nicht verfügbar ist. Angesichts der unendlichen Permutationen von Adressen wird eigentlich erwartet, dass der Ausnahmefall darin besteht, etwas zu geben, das funktioniert.

@dotnetchris Da es sich so anhört, als hätten Sie eine große Leidenschaft für dieses Thema, warum nehmen Sie sich nicht die Zeit, Ihre vorgeschlagene vollständige API für HttpClient2 zu verfassen und sie dem Team zur Diskussion als neues Problem vorzulegen?

Ich habe mich nicht darum gekümmert, was HttpClient auf einer tieferen technischen Ebene gut oder schlecht macht, also habe ich den Kommentaren von @dotnetchris nichts hinzuzufügen. Ich halte es definitiv nicht für angebracht, das MS-Entwicklerteam zu kritisieren, aber ich verstehe auch, wie frustrierend es ist, stundenlang nach Lösungen für Situationen zu suchen, die scheinbar super einfach zu handhaben sein sollten. Es ist die Frustration, darum zu kämpfen, zu verstehen, warum bestimmte Entscheidungen/Änderungen getroffen wurden, in der Hoffnung, dass sich jemand bei MS um die Bedenken kümmert, während Sie immer noch versuchen, Ihre Arbeit in der Zwischenzeit zu erledigen und zu liefern.

In meinem Fall habe ich einen externen Dienst, den ich während des OAuth-Prozesses aufrufe, um Benutzerrollen einzurichten, und wenn dieser Aufruf innerhalb einer bestimmten Zeitspanne abläuft (was leider häufiger vorkommt, als ich möchte), muss ich zu einer sekundären Methode zurückkehren .

Es gibt wirklich unzählige andere Gründe, warum jemand zwischen einer Zeitüberschreitung und einer Stornierung unterscheiden möchte. Warum es keine bessere Unterstützung für ein gängiges Szenario wie dieses zu geben scheint, ist etwas, das ich nur schwer verstehe, und ich hoffe wirklich, dass das MS-Team darüber nachdenkt.

@karelz Was wäre erforderlich, um dies für 3.0 planmäßig zu erreichen, ein API-Vorschlag von jemandem?

Hier geht es nicht um API-Vorschläge. Hier geht es darum, die richtige Umsetzung zu finden. Es befindet sich in unserem 3.0-Backlog und ziemlich weit oben auf der Liste (hinter HTTP/2 und Unternehmensszenarien). Es ist eine sehr gute Chance, von unserem Team in 3.0 angesprochen zu werden.
Wenn jemand in der Community motiviert ist, nehmen wir natürlich auch einen Beitrag.

@karelz wie kann die Community in diesem Fall helfen? Der Grund, warum ich erwähnt habe, ob ein API-Vorschlag erforderlich war, ist, dass ich mir aufgrund Ihrer Nachricht, die mit „Wir haben mehrere Optionen, was zu tun ist, wenn eine Zeitüberschreitung auftritt:“ beginnt, nicht sicher war, ob sich das Team für seine bevorzugte Alternative zu den von Ihnen aufgelisteten entschieden hatte. Wenn Sie sich für eine Richtung entschieden haben, können wir von dort aus gehen. Danke!

Technisch gesehen handelt es sich nicht um eine API-Vorschlagsfrage. Die Entscheidung erfordert keine API-Genehmigung. Ich stimme jedoch zu, dass wir uns darauf einigen sollten, wie der Unterschied sichtbar gemacht werden kann - es wird wahrscheinlich einige Diskussionen geben.
Ich erwarte nicht, dass sich die Optionen in der Implementierung zu sehr voneinander unterscheiden -- wenn die Community helfen möchte, wird uns die Implementierung einer der Optionen zu 98 % dorthin bringen (vorausgesetzt, die Ausnahme wird von einer Stelle ausgelöst und kann es sein kann später leicht geändert werden, um sie an die oben aufgeführten [1]-[3]-Optionen anzupassen).

Hallo! Auch wir sind kürzlich auf dieses Problem gestoßen. Nur neugierig, ob es immer noch auf Ihrem Radar für 3.0 ist?

Ja, es ist immer noch auf unserer 3.0-Liste - ich würde ihm immer noch eine Chance von 90 % geben, dass es in 3.0 behandelt wird.

Ich bin heute darauf gestoßen. Ich habe ein Timeout für HttpClient festgelegt und es gibt mir das

{System.Threading.Tasks.TaskCanceledException: A task was canceled.}
CancellationRequested = true

Ich stimme mit anderen überein, dass dies verwirrend ist, da es nicht erwähnt, dass Timeout das Problem ist.

Ähm, ok, ich bin etwas verwirrt. Jeder in diesem Thread scheint zu sagen, dass HTTP-Zeitüberschreitungen ein TaskCanceledException auslösen, aber das bekomme ich nicht ... Ich bekomme ein OperationCanceledException in .NET Core 2.1 ...

Wenn Sie eine OperationCanceledException für Timeouts auslösen möchten, kann ich das gut umgehen, aber ich habe nur Stunden damit verbracht, nachzuvollziehen, was zum Teufel vor sich ging, weil es nicht richtig dokumentiert ist:

https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.sendasync

Es besagt eindeutig, dass bei Timeouts ein HttpRequestException geworfen wird...

TaskCanceledException leitet sich von OperationCanceledException ab.

@stephentoub Ja, aber ich bekomme kein TaskCanceledException , ich bekomme ein OperationCanceledException .

Wie in, fängt ein Catch-Block, der TaskCanceledException abfängt, die Ausnahme nicht ab, er benötigt speziell einen Catch-Block für ein OperationCanceledException .

Ich teste jetzt speziell auf HTTP-Timeouts, indem ich nach der Art der Ausnahme filtere, und ich weiß, dass es sich um ein Timeout handelt, wenn es KEIN TaskCanceledException ist:

```c#
Versuchen {
using (var response = await s_httpClient.SendAsync(request, _updateCancellationSource.Token).ConfigureAwait(false))
Rückgabe (int)response.StatusCode < 400;
}
catch (OperationCanceledException ex) when (ex.GetType() != typeof(TaskCanceledException)) {
// HTTP-Zeitüberschreitung
falsch zurückgeben;
}
catch (HttpRequestException) {
falsch zurückgeben;
}

or alternatively this works as well, which might be better:

```c#
            try {
                using (var response = await s_httpClient.SendAsync(request, _updateCancellationSource.Token).ConfigureAwait(false))
                    return (int)response.StatusCode < 400;
            }
            catch (OperationCanceledException ex) when (ex.GetType() == typeof(OperationCanceledException)) {
                // HTTP timeout
                return false;
            }
            catch (HttpRequestException) {
                return false;
            }

Ähm, ok, ich bin etwas verwirrt. Jeder in diesem Thread scheint zu sagen, dass HTTP-Zeitüberschreitungen ein TaskCanceledException auslösen, aber das bekomme ich nicht ... Ich bekomme ein OperationCanceledException in .NET Core 2.1 ...

Wenn Sie eine OperationCanceledException für Timeouts auslösen möchten, kann ich das gut umgehen, aber ich habe nur Stunden damit verbracht, nachzuvollziehen, was zum Teufel vor sich ging, weil es nicht richtig dokumentiert ist:

https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.sendasync

Es besagt eindeutig, dass bei Timeouts ein HttpRequestException geworfen wird...

Haben Sie den gesamten Thread gelesen, weil es tatsächlich mehrere Antworten gibt, in denen auf OperationCanceledExecption verwiesen wird, einschließlich des von mir gestellten Codebeispiels, das einen Weg zeigt (nicht so sauber, wie ich möchte, aber es ist ein Weg), Timeouts explizit zu behandeln.

@Hobray Ja, das habe ich - was lässt dich glauben, dass ich es nicht getan habe? Das erklärt immer noch nicht, wie viele Leute in diesem Thread angeblich TaskCanceledException bei Timeouts bekommen, aber ich nicht.

Ich habe auch Ihr Codebeispiel gesehen, aber leider enthält es eine Race-Condition, die Sie meiner Meinung nach nicht berücksichtigt haben. Der Code, den ich verwende, vermeidet dieses Problem und erfordert keinen Zugriff auf das Abbruchtoken, was wichtig sein kann, wenn Sie versuchen, das Timeout weiter stromaufwärts abzufangen.

@mikernet Vielleicht hat mich die Formulierung abgeschreckt. Du hast recht, dass ich eine mögliche Rennbedingung nicht berücksichtigt hatte. Mir ist klar, dass es keine großartige Lösung ist, aber wenn Sie erklären möchten, was ich übersehen habe, würde ich es gerne hören.

Eine Sache, die mir aufgefallen ist, ist, dass die genaue Ausnahme, die Sie erhalten, zu variieren scheint, je nachdem, ob die Zeitüberschreitung oder der Abbruch in einem Controller vs. Middleware-Aufruf auftritt oder nicht. Innerhalb eines Middleware-Aufrufs scheint es nur TaskCanceledException zu sein. Leider habe ich nicht die Zeit, die .NET Core-Codebibliotheken zu durchsuchen, um zu verstehen, warum, aber es ist etwas, das mir aufgefallen ist.

@Hobray Haha ja natürlich - Entschuldigung, ich wollte nicht kryptisch über die Rennbedingungen sein, ich war gerade auf meinem Telefon, während ich unterwegs war, um diese letzte Nachricht einzugeben, was es schwierig machte, sie zu erklären.

Die Race-Bedingung in Ihrer Lösung tritt ein, wenn zwischen dem HttpClient -Timeout und dem Punkt, an dem Sie den Abbruchstatus des Abbruchtokens überprüfen, ein Aufgabenabbruch initiiert wird. In meiner Situation benötige ich eine Task-Abbruch-Ausnahme, um weiter zu sprudeln, damit der Upstream-Code den Benutzerabbruch erkennen kann. Daher muss ich die Ausnahme nur dann abfangen können, wenn es sich tatsächlich um eine Zeitüberschreitung handelt - deshalb verwende ich when um den Fang zu filtern. Wenn Sie Ihre Lösung zum Überprüfen des Tokens verwenden, kann dies dazu führen, dass ein unbehandeltes OperationCanceledException den Filter passiert und hochsprudelt und das Programm zum Absturz bringt, da jemand, der die Operation stromaufwärts abbricht, erwartet, dass ein TaskCanceledException geworfen wird, nicht ein OperationCanceledException .

Wenn der Code, der die HttpClient -Methode aufruft, sowohl das Timeout als auch den Task-Abbruch behandeln soll, dann ist die Unterscheidung zwischen Timeout und Benutzerabbruch, wenn die Race-Bedingung eintritt, je nach Situation für Sie möglicherweise nicht wichtig ... aber wenn Sie Code auf Bibliotheksebene schreiben und die Aufgabenstornierung auf einer höheren Ebene verarbeiten lassen müssen, macht dies definitiv einen Unterschied.

Wenn das, was Sie sagen, wahr ist und Zeitüberschreitungen je nach aufrufendem Kontext unterschiedliche Ausnahmetypen auslösen, scheint mir das definitiv ein Problem zu sein, das angegangen werden muss. Das ist oder sollte auf keinen Fall das beabsichtigte Verhalten sein.

Interessant auf die mögliche Rennbedingung. Das hatte ich nicht bedacht. Zum Glück wäre es für meinen Anwendungsfall kein großes Problem.

Was Ausnahmevariationen betrifft, muss ich etwas Zeit finden, um die Middleware, an der ich gearbeitet habe, zu verdummen, um zu sehen, ob sie mit dem übereinstimmt, was ich einige Male beim Debuggen gesehen habe. Die dummen Chrome-Omnibox-Preload-Aufrufe waren dort, wo ich sie in meiner Middleware gesehen habe, wenn das Timing genau richtig war. Ich denke, ich kann ein einfaches Beispiel erstellen, um es definitiv auf die eine oder andere Weise zu beweisen.

Mir ist immer noch unklar, was der richtige Workaround sein sollte.

Meine Lösung ist die richtigste in Situationen, in denen HTTP-Zeitüberschreitungen OperationCanceledException auslösen. Ich kann die Situation, in der TaskCanceledException ausgelöst wird, nicht reproduzieren, und niemand hat eine Möglichkeit bereitgestellt, dieses Verhalten zu reproduzieren, also sage ich, testen Sie Ihre Umgebung, und wenn sie mit meiner übereinstimmt, ist das die richtige Lösung.

Wenn in einigen Ausführungskontexten eine TaskCanceledException ausgelöst wird, schlägt meine Lösung als Allzwecklösung für diese Kontexte fehl und die andere vorgeschlagene Lösung ist die „beste“ Lösung, enthält aber eine Race-Bedingung, die für Ihre Situation wichtig sein kann oder nicht Sie zu bewerten.

Ich würde gerne mehr Details von jemandem hören, der behauptet, dass er TaskCanceledExceptions erhält, da dies eindeutig ein großer Fehler wäre. Ich bin etwas skeptisch, dass dies tatsächlich geschieht.

@mikernet ob TaskCanceledException oder OperationCanceledException geworfen wird, ist weitgehend irrelevant. Das ist ein Implementierungsdetail, auf das Sie sich nicht verlassen sollten. Was Sie tun können, ist, OperationException abzufangen (was auch TaskCanceledException abfängt) und zu prüfen, ob das von Ihnen übergebene Abbruch-Token storniert ist, wie in Hobrays Kommentar (https://github.com/) beschrieben. dotnet/corefx/issues/20296#issuecomment-449510983).

@thomaslevesque Ich habe bereits angesprochen, warum dieser Ansatz ein Problem ist. Ohne darauf einzugehen, ist Ihr Kommentar nicht sehr nützlich - es handelt sich lediglich um eine Wiederholung von Aussagen, bevor ich mich der Konvertierung angeschlossen habe, ohne das von mir beschriebene Problem anzugehen.

Was "das ist ein Implementierungsdetail" angeht - ja, aber leider muss ich mich darauf verlassen, da es sonst keine gute Möglichkeit gibt, festzustellen, ob die Aufgabe tatsächlich abgebrochen wurde oder das Zeitlimit überschritten wurde. Es sollte zu 100 % eine Möglichkeit geben, anhand der Ausnahme zu bestimmen, welches dieser beiden Szenarien aufgetreten ist, um die beschriebene Race-Bedingung zu vermeiden. OperationCanceledException ist aus diesem Grund nur eine schlechte Wahl ... es gibt bereits ein TimeoutException im Framework, das einen besseren Kandidaten darstellt.

Ausnahmen für asynchrone Aufgaben sollten sich nicht auf die Überprüfung von Daten in einem anderen Objekt verlassen, das ebenfalls asynchron geändert werden kann, um die Ursache der Ausnahme zu bestimmen, wenn ein häufiger Anwendungsfall darin besteht, eine Verzweigungsentscheidung basierend auf der Ursache der Ausnahme zu treffen. Das schreit nach Race-Condition-Problemen.

Auch im Hinblick darauf, dass der Ausnahmetyp ein Implementierungsdetail ist ... Ich glaube nicht wirklich, dass dies für Ausnahmen gilt, zumindest nicht in Bezug auf gut etablierte .NET-Dokumentationspraktiken. Nennen Sie ein weiteres Beispiel in .NET Framework, in dem die Dokumentation „Löst Ausnahme X aus, wenn Y eintritt“ und das Framework tatsächlich eine öffentlich zugängliche Unterklasse dieser Ausnahme auslöst, ganz zu schweigen von einer öffentlich zugänglichen Unterklasse, die für einen völlig anderen Ausnahmefall verwendet wird die gleiche Methode ! Das scheint nur ein schlechter Stil zu sein, und ich bin in meinen 15 Jahren .NET-Programmierung noch nie zuvor darauf gestoßen.

Unabhängig davon sollte TaskCanceledException für das Abbrechen von Aufgaben reserviert sein, die vom Benutzer initiiert wurden, nicht für Zeitüberschreitungen - wie gesagt, dafür haben wir bereits eine Ausnahme. Bibliotheken sollten alle internen Aufgabenabbrüche erfassen und geeignetere Ausnahmen auslösen. Ausnahmehandler für Aufgabenabbrüche, die mit Aufgabenabbruchtoken initiiert wurden, sollten nicht für die Behandlung anderer unabhängiger Probleme verantwortlich sein, die während des asynchronen Vorgangs auftreten können.

@mikernet Entschuldigung, ich habe diesen Teil der Diskussion verpasst. Ja, es gibt eine Rennbedingung, an die ich nicht gedacht hatte. Aber es ist vielleicht kein so großes Problem, wie es scheint ... Die Race-Bedingung tritt auf, wenn das Abbruchtoken signalisiert wird, nachdem das Timeout eintritt, aber bevor Sie den Status des Tokens überprüfen, richtig? In diesem Fall spielt es keine Rolle, ob ein Timeout aufgetreten ist, wenn die Operation ohnehin abgebrochen wird. Sie lassen also ein OperationCanceledException sprudeln und lassen den Anrufer glauben, dass die Operation erfolgreich abgebrochen wurde, was eine Lüge ist, da tatsächlich eine Zeitüberschreitung aufgetreten ist, aber dem Anrufer ist es egal, da er es wollte brechen Sie den Vorgang trotzdem ab.

Es sollte zu 100 % eine Möglichkeit geben, anhand der Ausnahme zu bestimmen, welches dieser beiden Szenarien aufgetreten ist, um die beschriebene Race-Bedingung zu vermeiden. OperationCanceledException ist aus diesem Grund nur eine schlechte Wahl ... es gibt bereits ein TimeoutException im Framework, das einen besseren Kandidaten darstellt.

Ich stimme dem vollkommen zu, das aktuelle Verhalten ist eindeutig falsch. Meiner Meinung nach ist die beste Option, um das Problem zu beheben, Option 1 aus dem Kommentar von @karelz . Ja, es ist eine bahnbrechende Änderung, aber ich vermute, dass die meisten Codebasen dieses Szenario sowieso nicht richtig handhaben, also wird es die Dinge nicht noch schlimmer machen.

@thomaslevesque Nun ja, aber ich habe das irgendwie angesprochen. Es kann für einige Anwendungen wichtig sein oder auch nicht. Bei meinem ist es aber so. Der Benutzer meiner Bibliotheksmethode erwartet nicht OperationCanceledException , wenn er eine Aufgabe abbricht - er erwartet TaskCanceledException und sollte in der Lage sein, das abzufangen und "sicher" zu sein. Wenn ich das Abbruchtoken überprüfe, sehe, dass es abgebrochen wurde, und die Ausnahme weitergebe, dann habe ich gerade eine Ausnahme weitergegeben, die von ihrem Handler nicht abgefangen wird und das Programm zum Absturz bringt. Meine Methode soll keine Timeouts auslösen.

Sicher, es gibt Möglichkeiten, dies zu umgehen ... Ich kann einfach ein neues umschlossenes TaskCanceledException werfen, aber dann verliere ich den Stack-Trace der obersten Ebene, und HTTP-Zeitüberschreitungen werden in meinem Dienst ganz anders behandelt als Aufgabenabbrüche - HTTP-Zeitüberschreitungen gehen für lange Zeit in einen Cache, während Aufgabenabbrüche dies nicht tun. Ich möchte das Zwischenspeichern tatsächlicher Timeouts aufgrund der Race-Bedingung nicht verpassen.

Ich denke, ich könnte OperationCanceledException abfangen und das Abbruchtoken überprüfen. Wenn es abgebrochen wurde, überprüfen Sie auch die Art der Ausnahme, um sicherzustellen, dass es sich um ein TaskCanceledException handelt, bevor Sie es erneut auslösen, um zu vermeiden, dass das ganze Programm abstürzt. Wenn eine dieser Bedingungen falsch ist, muss es sich um eine Zeitüberschreitung handeln. Das einzige Problem, das in diesem Fall übrig bleibt, ist die Race-Bedingung, die subtil ist, aber wenn Sie Tausende von HTTP-Anforderungen pro Minute auf einem stark ausgelasteten Dienst mit engen Timeouts haben, passiert es.

So oder so, das ist hacky und wie Sie angegeben haben, sollte es irgendwie gelöst werden.

(Die Race-Bedingung tritt nur in Umgebungen auf, in denen TaskCanceledException anstelle von OperationCanceledException geworfen wird, wo auch immer das sein mag, aber zumindest ist die Lösung sicher davor, einen unerwarteten Ausnahmetyp hervorzurufen und tut es nicht sich auf Implementierungsdetails verlassen, um immer noch einigermaßen gut zu funktionieren, wenn sich die Implementierungsdetails ändern).

Die Timeout-Ausnahme, die von HttpClient im Falle eines HTTP-Timeouts ausgelöst wird, sollte nicht vom Typ System.Net.HttpRequestException (oder ein Nachkomme?) sein, anstatt vom allgemeinen System.TimeoutException ? Zum Beispiel wirft das gute alte WebClient WebException mit einer Eigenschaft Status , die auf WebExceptionStatus.Timeout gesetzt werden kann.

Leider können wir dies nicht für 3.0 beenden. Aufbruch in die Zukunft.
Es ist immer noch ganz oben in unserem Rückstand, aber realistischerweise werden wir es nicht für 3.0 schaffen :(.

@karelz oh nein :( Ich wollte gerade sagen, wie ich bei der zweiten Firma in Folge wieder darauf gestoßen bin. Wie lange würde das Zeitfenster sein, um eine PR für 3.0 einzureichen, wenn ich es selbst angehen wollte? Cheers

Es fällt mir schwer, genau zu verfolgen, was in den obigen Kommentaren vor sich geht, wenn Leute je nach Kontext Unterschiede zwischen dem Erhalten von TaskCancelledException und OperationCancelledException melden. Kann jemand von MSFT das beleuchten?

Gefällt mir dieser Kommentar von @mikernet :

Wie in, fängt ein Catch-Block, der TaskCanceledException abfängt, die Ausnahme nicht ab, er benötigt speziell einen Catch-Block für eine OperationCanceledException.

cc @davidsh @stephentoub vielleicht?

TaskCanceledException leitet sich von OperationCanceledException ab und enthält einige zusätzliche Informationen. Fangen Sie einfach OperationCanceledException ab.

@karelz wäre Option 1, die Sie ursprünglich gepostet haben und nur eine Timeout-Ausnahme auslöst, völlig unangenehm?

Dinge wie StackExchange.Redis lösen eine Ausnahme aus, die von der Timeout-Ausnahme abgeleitet ist. Deshalb wäre es schön, nur die Timeout-Ausnahme für HttpClient abzufangen ...

Ich verstehe @stephentoub angesichts der Erbschaft, nur dieser Kommentar unten von @mikernet haut mich um ... Dieses Verhalten, das er beschreibt, müsste eine Art Fehler sein?? Aber wenn es wahr wäre, würde es die Lösung oder Problemumgehung beeinflussen …

"ein Catch-Block, der TaskCanceledException abfängt, fängt die Ausnahme nicht ab, er benötigt speziell einen Catch-Block für eine OperationCanceledException"

Oder sagen Sie, dass es an einigen Stellen eine OperationEx und an anderen Stellen eine TaskEx auslöst? Vielleicht sehen die Leute etwas, das von ASP-Middleware kommt, und denken, es kommt von HttpClient?

nur dieser Kommentar unten von @mikernet haut mich um ... Dieses Verhalten, das er beschreibt, müsste eine Art Fehler sein??

Warum ist das „umwerfend“? Wenn Ausnahme B von Ausnahme A abgeleitet wird, fängt ein Catch-Block für B keine Ausnahme ab, die konkret A ist, z.

Oder sagen Sie, dass es an einigen Stellen eine OperationEx und an anderen Stellen eine TaskEx auslöst?

Richtig. Im Allgemeinen sollte Code nur OCE annehmen; So wie die Dinge geschrieben sind, kann es vorkommen, dass Code das abgeleitete TCE auslöst, z. B. erzeugt eine über TaskCompletionSource.TrySetCanceled abgeschlossene Aufgabe eine TaskCanceledException.

Danke Stephen, nach den Geräuschen im Thread hatten mehrere Leute den Eindruck, dass TaskCE überall hingeworfen wurde, was das von mikernet erwähnte Verhalten keinen Sinn ergeben hätte ... Jetzt, da Sie klargestellt haben, macht es seins Beobachtung logisch :)

@stephentoub , ist diese Anleitung irgendwo dokumentiert? Pingen Sie @guardrex

Im Allgemeinen sollte Code nur OCE annehmen ...

Ich arbeite heutzutage mit dem ASP.NET-Dokumentsatz. Eröffnen Sie ein Problem für die dotnet/docs-Katzen ...

https://github.com/dotnet/docs/issues

Ist es möglich, dieses Gespräch wieder aufzunehmen, wo es einige konkrete Vorschläge mit @karelz und @davidsh gab ? Es fühlt sich einfach so an, als müssten jemand, der Eigentümer des HttpClient-API-Modells ist, Entscheidungen über die einzuschlagende Richtung treffen. Es gab einige Diskussionen darüber, dass Ausnahmen der obersten Ebene immer HttpRequestException sind (außer expliziten Abbrüchen??), gemischt mit einem längerfristig erscheinenden Thema über einen größeren Refactor, um HttpRequestion so zu ändern, dass eine neue Enum-Eigenschaft ähnlich wie WebExceptionStatus enthalten ist ...

Es fühlt sich einfach so an, als ob dieses ganze Problem im Verhältnis zu der tatsächlich erforderlichen Codeänderung squirreled wird. Gibt es andere Personen, die einen Anteil/Eigentum am HttpClient-API-Modell haben, das eingeschleift werden sollte, um einige Entscheidungen zu treffen? Tausend Dank an alle!

Ich habe hier das gleiche Problem - cancellationToken.IsCancellationRequested gibt jedoch true zurück:

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

                return response;
            }
            catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
            {
                throw new TimeoutException("Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.");
            }
        }

Ich erzwinge eine Auszeit. Die ausgelöste Ausnahme ist TaskCancelledException mit IOException als innerer Ausnahme:

System.Threading.Tasks.TaskCanceledException: The operation was canceled. ---> System.IO.IOException: Unable to read data from the transport connection: A operação de E/S foi anulada devido a uma saída de thread ou a uma requisição de aplicativo. ---> System.Net.Sockets.SocketException: A operação de E/S foi anulada devido a uma saída de thread ou a uma requisição de aplicativo
   --- End of inner exception stack trace ---
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.GetResult(Int16 token)
   at System.Net.Http.HttpConnection.FillAsync()
   at System.Net.Http.HttpConnection.ReadNextResponseHeaderLineAsync(Boolean foldedHeadersAllowed)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithNtConnectionAuthAsync(HttpConnection connection, HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

...

(Entschuldigung für den übersetzten Ausnahmetext – anscheinend dachte jemand, es wäre eine gute Idee, dies auch in .NET Core zu bringen.)

Wie Sie sehen können, hatte ich gehofft, IsCancellationRequest zu überprüfen, um dieses Problem zu umgehen, wie andere vorgeschlagen haben, aber selbst bei Zeitüberschreitungen wird true zurückgegeben.

Derzeit denke ich darüber nach, einen Timer zu implementieren, um zu überprüfen, ob das konfigurierte Timeout abgelaufen ist. Aber offensichtlich will ich sterben , das ist eine schreckliche Problemumgehung.

Hallo @FelipeTaveira ,

Ihr Ansatz funktioniert nicht, da die Stornierung vor Ihrem TimeoutHandler von HttpClient selbst abgewickelt wird. Ihr Handler erhält ein Abbruchtoken, das von HttpClient abgebrochen wird, wenn ein Timeout auftritt. Die Überprüfung, ob das Abbruchtoken nicht abgebrochen wurde, funktioniert also nur in Code, der HttpClient aufruft, nicht in Code, der von HttpClient aufgerufen wird.

Um es mit einem benutzerdefinierten Handler zum Laufen zu bringen, deaktivieren Sie Timeout von HttpClient , indem Sie es auf Timeout.InfiniteTimeSpan setzen und das Timeout-Verhalten in Ihrem eigenen Handler steuern, wie in diesem Artikel gezeigt (vollständiger Code hier )

@thomaslevesque Oh, danke! Ich hatte Ihren Code durchgelesen und dachte, dass es in diesem Teil um die Unterstützung der Timeout-Konfiguration pro Anfrage ging, und hatte nicht bemerkt, dass es auch notwendig war, das TimeoutException -Ding zu unterstützen.

Ich hoffe, dass dies in Zukunft angegangen wird.

Für das, was es wert ist, finde ich es auch verwirrend, dass TaskCanceledException auf Zeitüberschreitungen geworfen wird.

Ich kann bestätigen, dass TaskCanceledExceptions eine große Quelle der Verwirrung war, als ich mich in meinem vorherigen Job mit Produktionsproblemen befasste. Wir würden im Allgemeinen Aufrufe an HttpClient umbrechen und gegebenenfalls TimeoutExceptions auslösen.

@eiriktsarpalis danke. Wir planen, es in 5.0 anzugehen, sodass Sie es in diesem Zeitrahmen abholen können, wenn Sie möchten, jetzt, wo Sie unserem Team beigetreten sind 😇.

1 Jahr? 😵 Wo ist die versprochene Agilität von .NET Core?

Die Agilität des gesamten Projekts anhand eines Problems zu beurteilen, ist ein bisschen ... unfair.
Ja, es ist ein bisschen peinlich, wir haben dieses Problem immer noch, aber es ist schwer zu beheben (mit Auswirkungen auf Kompatibilität) und es gab nur höherpreisige Geschäftsziele, die zuerst angegangen werden mussten (im Netzwerkbereich) – sehen Sie sich die Änderungen an Meilensteinen im Laufe der Geschichte an dieses Problems.

Übrigens: Wie in vielen anderen OSS-Projekten gibt es keine SLA oder ETAs oder Versprechen, wann das Problem behoben wird. Alles hängt von Prioritäten, anderen Arbeiten, Schwierigkeiten und der Anzahl der Experten ab, die das Problem angehen können.

Die Agilität des gesamten Projekts anhand eines Problems zu beurteilen, ist ein bisschen ... unfair.

Nun, es war eine ehrliche Frage. Es ist schon zwei Jahre her und als ich gesehen habe, dass es noch eines dauern wird, war ich etwas überrascht. Ist aber nicht böse gemeint 🙂. Ich weiß, dass es zur Behebung dieses Problems einige Fragen gab/gibt, die zuerst beantwortet werden mussten/müssen.

In einer Welt der Kompromisse ... Ich mag es nicht, aber hier ist eine andere Option zum Kauen:

Wir könnten weiterhin TaskCanceledException werfen, aber CancellationToken auf einen Sentry-Wert setzen.

c# catch(TaskCanceledException ex) when(ex.CancellationToken == HttpClient.TimeoutToken) { }

Und noch etwas: Wirf ein neues HttpTimeoutException , das von TaskCanceledException abgeleitet ist.

Und noch etwas: Wirf ein neues HttpTimeoutException , das von TaskCanceledException abgeleitet ist.

Ich denke, das würde alle Bedenken ausräumen. Es macht es einfach, ein Timeout speziell abzufangen, und es ist immer noch ein TaskCanceledException , sodass Code, der derzeit diese Ausnahme abfängt, auch die neue abfängt (keine Breaking Change).

Und noch etwas: Wirf eine neue HttpTimeoutException, die von TaskCanceledException abgeleitet ist.

Es scheint ziemlich hässlich, aber wahrscheinlich ein guter Kompromiss.

Und noch etwas: Wirf ein neues HttpTimeoutException , das von TaskCanceledException abgeleitet ist.

Ich denke, das würde alle Bedenken ausräumen. Es macht es einfach, ein Timeout speziell abzufangen, und es ist immer noch ein TaskCanceledException , sodass Code, der derzeit diese Ausnahme abfängt, auch die neue abfängt (keine Breaking Change).

Nun, es _könnte_ da draußen Leute geben, die Code wie diesen schreiben:

try
{
  // ...
}
catch (Exception ex) when (ex.GetType() == typeof(TaskCanceledException))
{
}

Das wäre also technisch gesehen eine bahnbrechende Änderung.

Ich denke auch, dass es ein bisschen das Schlimmste aus beiden Welten hat. Wir werden immer noch keine allgemeinen TimeoutException bekommen, wenn wir gleichzeitig eine Breaking Change haben.

Es könnte Leute da draußen geben, die solchen Code schreiben, also wäre dies technisch gesehen eine bahnbrechende Änderung

FWIW, so ziemlich jede Änderung bricht möglicherweise zusammen, also muss man irgendwo eine Grenze ziehen, um Fortschritte bei irgendetwas zu machen. In Corefx betrachten wir das Zurückgeben oder Werfen eines stärker abgeleiteten Typs nicht als brechend.
https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/breaking-change-rules.md#exceptions

In einer Welt der Kompromisse ... Ich mag es nicht, aber hier ist eine andere Option zum Kauen:

Wir könnten weiterhin TaskCanceledException werfen, aber CancellationToken auf einen Sentry-Wert setzen.

catch(TaskCanceledException ex) when(ex.CancellationToken == HttpClient.TimeoutToken)
{
}

Ich mag dieses wirklich. Gibt es noch andere Nachteile als "hässlich" zu sein? Was ist heute ex.CancellationToken , wenn eine Zeitüberschreitung auftritt?

Gibt es noch andere Nachteile als "hässlich" zu sein?

Es wäre wirklich einfach für jemanden zu glauben, dass TimeoutToken herumgereicht und verwendet werden kann, um einen Abbruch zu signalisieren, wenn ein Timeout auftritt, da dies das Design ist, das an anderer Stelle für solche Dinge verwendet wird.

Ich mag dieses wirklich. Gibt es noch andere Nachteile als "hässlich" zu sein? Was ist heute ex.CancellationToken , wenn eine Zeitüberschreitung auftritt?

Es ist viel weniger intuitiv als das Abfangen eines bestimmten Ausnahmetyps, der in der Dokumentation ausdrücklich erwähnt werden kann.

Ich bin es gewohnt, OperationCanceledException und abgeleitete Typen zu betrachten, um eine unterbrochene Ausführung anzuzeigen, nicht einen Anwendungsfehler. Die Ausnahme, die HttpClient bei Zeitüberschreitung auslöst, ist normalerweise ein Fehler – es gab einen Aufruf an eine HTTP-API und sie war nicht verfügbar. Dieses Verhalten führt entweder zu Komplikationen bei der Verarbeitung von Ausnahmen auf einer höheren Ebene, z. B. in ASP.NET Core-Pipeline-Middleware, da dieser Code wissen muss, dass es zwei Arten von OperationCanceledException gibt, Fehler und Nicht-Fehler. oder es zwingt Sie, jeden HttpClient -Aufruf in einen Throw-Catch-Block zu packen.

Und noch etwas: Wirf eine neue HttpTimeoutException, die von TaskCanceledException abgeleitet ist.

Es scheint ziemlich hässlich, aber wahrscheinlich ein guter Kompromiss.

Ist es angesichts der damit verbundenen Designbeschränkungen und Abwärtskompatibilitätsüberlegungen wirklich so hässlich? Ich weiß nicht, dass jemand in diesem Thread eine offensichtlich überlegene Lösung vorgeschlagen hat, die keine Nachteile hat (ob Verwendung oder Reinheit des Designs usw.).

Wir wollen uns das genauer ansehen, als nur den Ausnahmetyp zu ändern. Es gibt auch ein seit langem bestehendes Problem: "Wo haben wir eine Zeitüberschreitung festgestellt?" – zum Beispiel ist es möglich, dass eine Anfrage eine Zeitüberschreitung erleidet, ohne dass sie jemals ans Netz gegangen ist – das müssen wir uns ansehen, und eine Lösung sollte mit der Richtung, in die wir gehen, kompatibel sein.

Wir wollen uns das genauer ansehen, als nur den Ausnahmetyp zu ändern. Es gibt auch ein seit langem bestehendes Problem: "Wo haben wir eine Zeitüberschreitung festgestellt?" – zum Beispiel ist es möglich, dass eine Anfrage eine Zeitüberschreitung erleidet, ohne dass sie jemals ans Netz gegangen ist – das müssen wir uns ansehen, und eine Lösung sollte mit der Richtung, in die wir gehen, kompatibel sein.

Ist das wirklich etwas, das Sie wissen müssen? Ich meine, werden Sie den Fehler unterschiedlich behandeln, je nachdem, wo er abgelaufen ist? Für mich reicht es normalerweise aus, nur zu wissen, dass ein Timeout aufgetreten ist.
Es macht mir nichts aus, wenn Sie mehr tun möchten, ich möchte nur nicht, dass diese Änderung den .NET 5-Zug wegen zusätzlicher Anforderungen verpasst :wink:

Ich denke, das von @scalablecory angesprochene Problem ist bemerkenswert, aber ich stimme zu, dass es nicht daran hindern sollte, dass es früher behoben wird. Sie können sich für einen Mechanismus entscheiden, mit dem der Grund für die Zeitüberschreitung ausgewertet werden kann, aber bitte zögern Sie nicht, das Ausnahmeproblem dafür zu beheben ... Sie können der Ausnahme später immer eine Reason -Eigenschaft hinzufügen.

Ich stimme @thomaslevesque zu; Ich habe das noch nie als großes Problem gehört. Wo kommt das her?

Ist das ein eher Nischen-Debugging-Fall, in dem es in Ordnung wäre, den Grund zu untersuchen, warum ein wenig explizite Aktion erforderlich ist, während allgemeine Entwickler nur eine einzige Sache wissen / sich darum kümmern müssten?

Wir wollen uns das genauer ansehen, als nur den Ausnahmetyp zu ändern. Es gibt auch ein seit langem bestehendes Problem: "Wo haben wir eine Zeitüberschreitung festgestellt?" – zum Beispiel ist es möglich, dass eine Anfrage eine Zeitüberschreitung erleidet, ohne dass sie jemals ans Netz gegangen ist – das müssen wir uns ansehen, und eine Lösung sollte mit der Richtung, in die wir gehen, kompatibel sein.

Ich nehme an, die Implikation hierin ist, dass es in der Verantwortung jedes HttpMessageHandler liegen sollte, den richtigen Timeout-Ausnahmetyp auszulösen, anstatt dass HttpClient die Dinge repariert.

Ich nehme an, die Implikation hierin ist, dass es in der Verantwortung jedes HttpMessageHandler liegen sollte, den richtigen Timeout-Ausnahmetyp auszulösen, anstatt dass HttpClient die Dinge repariert.

Ich sehe nicht, wie das mit der heute definierten Abstraktion möglich ist. HttpMessageHandler hat keine Kenntnis von Timeout von HttpClient; Soweit ein HttpMessageHandler betroffen ist, wird ihm nur ein Abbruchtoken übergeben, das aus einer Reihe von Gründen eine Abbruchanforderung haben könnte, einschließlich des Signals des Abbruchtokens des Anrufers, des Aufrufs von CancelPendingRequests des HttpClient, des Ablaufs des erzwungenen Timeouts des HttpClient usw.

Ist das wirklich etwas, das Sie wissen müssen? Ich meine, werden Sie den Fehler unterschiedlich behandeln, je nachdem, wo er abgelaufen ist? Für mich reicht es normalerweise aus, nur zu wissen, dass ein Timeout aufgetreten ist.

Es ist nützlich zu wissen, ob der Server Ihre Anfrage verarbeitet hat. Es eignet sich auch hervorragend für die Diagnose – ein Remote-Server, der keine Zeitüberschreitung aufweist, der Client jedoch Zeitüberschreitungen meldet, ist eine frustrierende Erfahrung.

@davidsh hat vielleicht mehr Einblick.

Ich stimme @thomaslevesque zu; Ich habe das noch nie als großes Problem gehört. Wo kommt das her?

Es war ein Punkt, der während eines kürzlichen NCL-Treffens angesprochen wurde. Es kann sein, dass wir entscheiden, dass dies nicht wichtig genug ist, um die Dinge aufzudecken oder zu verzögern, aber es lohnt sich, zuerst eine kurze Diskussion zu führen.

@dotnet/ ncl @stephentoub Bringen wir das ins Bett. Wir wissen, dass alles, was wir tun, auf subtile Weise zerbrechen wird. Dies scheint die am wenigsten schlechte Option zu sein. Ich schlage vor, fortzufahren mit:

Wirf ein neues HttpTimeoutException , das von TaskCanceledException abgeleitet ist.

Einwände?

löst eine neue HttpTimeoutException aus, die von TaskCanceledException abgeleitet ist.

Wie machen wir das in der Praxis? Es bedeutet wahrscheinlich, dass wir unseren Code bereinigen und alle Stellen finden müssen, die CancellationToken.ThrowIfCancellationRequested aufrufen, und ihn stattdessen konvertieren müssen in:

c# if (token.IsCancellationRequested) throw new HttpTimeoutException(EXTRASTUFF, token);

sowie explizite Orte, an denen wir möglicherweise auch OperationCancelledException anrufen.

Gibt es eine Option, die es nicht erfordert, den gesamten zugehörigen Code sowieso durchzugehen?

Gibt es eine Option, die es nicht erfordert, den gesamten zugehörigen Code sowieso durchzugehen?

Wahrscheinlich nicht, aber es könnte Orte geben, die wir nicht kontrollieren (noch nicht wirklich sicher), und daher müssen wir möglicherweise eine zufällige TaskCancelledException/OperationCancelledException erfassen und die neue HttpTimeoutException erneut auslösen.

Es bedeutet wahrscheinlich, dass wir unseren Code bereinigen und alle Stellen finden müssen, die CancellationToken.ThrowIfCancellationRequested aufrufen, und ihn stattdessen konvertieren müssen in:

Gibt es eine Option, die es nicht erfordert, den gesamten zugehörigen Code sowieso durchzugehen?

Nicht genau.

Was die Handler anbelangt, erhalten sie lediglich ein CancellationToken: Sie wissen nicht und es ist ihnen egal, woher dieses Token stammt, wer es erstellt hat oder warum möglicherweise eine Stornierung angefordert wurde. Sie wissen nur, dass irgendwann eine Stornierung angefordert werden kann, und lösen als Antwort eine Stornierungsausnahme aus. Hier in den Handlern gibt es also nichts zu tun.

Es ist HttpClient selbst, der die relevante CancellationTokenSource erstellt und ein Timeout dafür festlegt:
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L488
Das Timeout ist vollständig die Erfindung von HttpClient selbst, und nur es weiß, ob es eine aufgetretene Abbruchausnahme als etwas Besonderes behandeln möchte.

Das bedeutet, dass der oben referenzierte Code umgestaltet werden müsste, um das Timeout separat nachzuverfolgen, entweder als separate CancellationTokenSource oder wahrscheinlicher durch eigenständige Behandlung der Timeout-Logik und zusätzlich zum Abbrechen des CTS auch durch Setzen eines Flags, das es kann dann auch prüfen. Dies wird wahrscheinlich kein Pay-for-Play sein, da wir hier zusätzliche Zuweisungen hinzufügen werden, aber aus der Code-Perspektive ist es relativ einfach.

Dann an Stellen in HttpClient wie:
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L536 -L541
und
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L562 -L566
Wenn es eine Ausnahme abfängt, müsste es einen Sonderfall abbrechen, um zu prüfen, ob die Zeitüberschreitung abgelaufen ist, und wenn dies der Fall war, davon ausgehen, dass dies die Ursache für den Abbruch war, und eine neue Ausnahme des gewünschten Typs auslösen.

Es müsste in einem Sonderfall abgebrochen werden, um zu überprüfen, ob das Timeout abgelaufen ist, und wenn dies der Fall war, davon ausgehen, dass dies die Ursache für den Abbruch war, und eine neue Ausnahme des gewünschten Typs auslösen.

Wie funktioniert das, wenn die HttpClient.Timeout-Eigenschaft nicht beteiligt ist? Vielleicht ist es auf "unendlich" eingestellt. Was ist mit Stornierungstoken, die direkt an die HttpClient-APIs (GetAsync, SendAync usw.) übergeben werden? Ich nehme an, das würde genauso funktionieren?

Wie funktioniert das, wenn die HttpClient.Timeout-Eigenschaft nicht beteiligt ist?

Ich verstehe die Frage nicht ... das ist die einzige Auszeit, von der wir hier sprechen. Wenn es auf Unendlich eingestellt ist, würde diese Zeitüberschreitung niemals zu einem Abbruch führen, und es wäre nichts zu tun. Wir haben bereits den Sonderfall Timeout == Infinite und würden weiterhin:
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L481

Was ist mit Stornierungstoken, die direkt an die HttpClient-APIs (GetAsync, SendAync usw.) übergeben werden? Ich nehme an, das würde genauso funktionieren?

Wir kombinieren jeden übergebenen Token mit einem unserer eigenen:
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L485
Wir würden unseren eigenen Umgang mit HttpClient.Timeout verfolgen, wie in meinen vorherigen Kommentaren erwähnt. Entscheidet sich der Anrufer dafür, ein Token zu übergeben, das er mit einem Timeout konfiguriert hat, dann liegt es an ihm, dies speziell zu behandeln: Für uns ist es nur eine Abbruchanfrage.

Einwände?

Warum einen neuen abgeleiteten Ausnahmetyp einführen, der mit der vorhandenen TimeoutException "konkurriert", anstatt beispielsweise eine Abbruchausnahme auszulösen, die eine TimeoutException als innere Ausnahme umschließt? Wenn das Ziel darin besteht, einem Verbraucher zu ermöglichen, programmgesteuert zu bestimmen, ob die Stornierung auf eine Zeitüberschreitung zurückzuführen ist, würde dies ausreichen? Es scheint die gleichen Informationen zu übermitteln, ohne eine neue Oberfläche zu benötigen.

Warum einen neuen abgeleiteten Ausnahmetyp einführen, der mit der vorhandenen TimeoutException "konkurriert", anstatt beispielsweise eine Abbruchausnahme auszulösen, die eine TimeoutException als innere Ausnahme umschließt?

Meistens aus Bequemlichkeit. Es ist einfacher und intuitiver zu schreiben

catch(HttpTimeoutException)

als

catch(OperationCanceledException ex) when (ex.InnerException is TimeoutException)

Davon abgesehen ist beides für mich in Ordnung, solange es einigermaßen einfach ist, eine Zeitüberschreitung zu identifizieren.

Sagen wir, eine Stornierungsausnahme auszulösen, die eine TimeoutException als innere Ausnahme umschließt?

Ich denke, das ist ein guter Kompromiss. Es würde helfen, Ausnahmeprotokolle eindeutig zu machen, was mich beim Umgang mit dieser Art von Zeitüberschreitungen am meisten ärgerte. Es kommuniziert wahrscheinlich auch besser die Tatsache, dass dies der HttpClient ist, der eine Anforderung abbricht, und nicht ein Timeout, das vom zugrunde liegenden Handler ausgelöst wird. Auch keine Breaking Changes.

Triage:

  • Wir werden dieses Szenario so aktualisieren, dass ein InnerException von TimeoutException mit einer Meldung für die Protokollierung/Diagnose vorhanden ist, die den Abbruch leicht als durch das Auslösen des Zeitlimits verursacht erkennt.
  • Wir werden die Dokumentation mit Anleitungen zu Möglichkeiten aktualisieren, wie man programmgesteuert prüfen kann, um Abbrüche zu unterscheiden: Überprüfung auf TimeoutException , Verwendung eines unendlichen Timeout und Übergabe eines CancellationToken mit einer Zeitüberschreitung usw.

Wir sind uns bewusst, dass jede Lösung hier einen Kompromiss erfordert, und glauben, dass dies ein gutes Maß an Kompatibilität mit vorhandenem Code bietet.

Warum einen neuen abgeleiteten Ausnahmetyp einführen, der mit der vorhandenen TimeoutException "konkurriert", anstatt beispielsweise eine Abbruchausnahme auszulösen, die eine TimeoutException als innere Ausnahme umschließt?

Meistens aus Bequemlichkeit. Es ist einfacher und intuitiver zu schreiben

catch(HttpTimeoutException)

als

catch(OperationCanceledException ex) when (ex.InnerException is TimeoutException)

Davon abgesehen ist beides für mich in Ordnung, solange es einigermaßen einfach ist, eine Zeitüberschreitung zu identifizieren.

@scalablecory Ich bin verblüfft, dass ich die Diskussion anscheinend um ein paar Stunden verpasst habe - meine Erfahrung bei der Kommunikation dieser Art von Dingen mit allgemeinen LoB .NET-Entwicklern ist, dass das Abfangen einer abgeleiteten HttpTimeoutException für die Leute viel einfacher zu verstehen/anzunehmen ist, als es zu müssen Denken Sie daran, einen Ausnahmefilter für eine innere Ausnahme zu verwenden. Nicht jeder weiß, was ein Ausnahmefilter ist.

Gleichzeitig erkenne ich voll und ganz an, dass dies eines dieser Szenarien ist, in denen es, wie Sie gesagt haben, keine eindeutige Lösung ohne Kompromisse gibt.

Wir waren uns in diesem Punkt einig, @ericsampson , aber das wäre für viele bestehende Benutzer ein Breaking Change. Wie @scalablecory erwähnte, sind wir Kompromisse eingegangen, um Verbesserungen vorzunehmen und gleichzeitig Kompatibilitätsschäden zu begrenzen.

Wenn ich über die Ausnahmen nachdenke, halte ich im Allgemeinen den Wrapping-Ansatz für besser. Das Ableiten einer Ausnahme von OperationCanceledException macht eine solche Ausnahme nur bei asynchroner Verwendung verfügbar. Und selbst es ist eng an Aufgaben. Wie wir in der Vergangenheit gesehen haben, gab es mehrere Entwicklungen von asynchronen Ansätzen, und ich betrachte die Aufgabe als Implementierungsdetail, aber TimeoutException ist beispielsweise Teil des Socket-Konzepts, das sich nicht ändern wird.

Wir waren uns in diesem Punkt einig, @ericsampson , aber das wäre für viele bestehende Benutzer ein Breaking Change. Wie @scalablecory erwähnte, sind wir Kompromisse eingegangen, um Verbesserungen vorzunehmen und gleichzeitig Kompatibilitätsschäden zu begrenzen.

Obwohl ich nicht unbedingt mit der Wrapping-Option nicht einverstanden bin, denke ich, dass es sich lohnt, darauf hinzuweisen, dass - soweit ich das verstanden habe - das Problem nicht darin besteht, dass die abgeleitete Option eine Breaking Change ist, sondern dass die Wrapping-Option einfacher ist. Wie @stephentoub zuvor in der Diskussion erwähnt hat, wird das Auslösen einer abgeleiteten Ausnahme von den Corefx-Richtlinien nicht als Breaking Change angesehen.

Das Ableiten von HttpTimeoutException von TaskCanceledException verletzt die Beziehung „ist ein“ zwischen diesen beiden Ausnahmen. Es lässt den aufrufenden Code trotzdem glauben, dass ein Abbruch stattgefunden hat, es sei denn, es behandelt speziell HttpTimeoutException. Dies ist im Grunde die gleiche Handhabung, die wir in dem Moment durchführen müssen, in dem wir prüfen, ob ein Abbruchtoken abgebrochen wird, um festzustellen, ob es sich um eine Zeitüberschreitung handelt oder nicht. Außerdem ist es verwirrend, zwei Timeout-Ausnahmen in der Kernbibliothek zu haben.
Das Hinzufügen von TimeoutException als innere Ausnahme zu TaskCanceledException liefert in den meisten Fällen keine zusätzlichen Informationen, da wir das Abbruchtoken bereits überprüfen und feststellen können, ob es sich um eine Zeitüberschreitung handelt oder nicht.

Ich denke, die einzig vernünftige Lösung besteht darin, die bei Timeout ausgelöste Ausnahme in TimeoutException zu ändern. Ja, es wird eine Breaking Change sein, aber das Problem erstreckt sich bereits über mehrere Major Releases und Breaking Changes sind das, wofür Major Releases da sind.

Kann die Dokumentation für vorhandene Versionen aktualisiert werden, um anzuzeigen, dass Methoden TaskCanceledException und/oder OperationCanceledException (beide konkrete Typen scheinen eine Möglichkeit zu sein) auslösen könnten, wenn ein Timeout auftritt? Derzeit weist die Dokumentation darauf hin, dass HttpRequestException mit Timeouts umgeht, was falsch und irreführend ist.

@qidydl das ist der Plan ... wird vollständig dokumentieren, wie sich Zeitüberschreitungen manifestieren können und wie man sie detailliert behandelt.

Danke @alnikola und @scalablecory und @davidsh und @karelz und allen anderen, die an dieser Diskussion/Implementierung beteiligt sind, ich weiß das wirklich zu schätzen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

jchannon picture jchannon  ·  3Kommentare

yahorsi picture yahorsi  ·  3Kommentare

aggieben picture aggieben  ·  3Kommentare

chunseoklee picture chunseoklee  ·  3Kommentare

GitAntoinee picture GitAntoinee  ·  3Kommentare