2016-12-07 5 views
2

Ich hatte ein Problem mit einem Hanging have (beschrieben here). Während der Forschung fand ich heraus, dass der Aufruf SetResult auf meinem TaskCompletionSource aufruft tatsächlich wartet auf Fortsetzung im Kontext des Threads, der SetResult genannt (dies wird auch in this answer zu einer etwas verwandten Frage geschrieben). In meinem Fall ist dies ein anderer Thread (Thread-Pool-Worker-Thread) als der, der das Warten gestartet hat (ein ASP.NET-Anforderungs-Thread).Manuelles Erfassen und Anwenden von SynchronizationContext beim Abschließen einer Task

Während ich immer noch nicht sicher bin, warum dies einen Hang verursachen würde, entschied ich mich zu versuchen, die SetResult in den ursprünglichen Kontext zu zwingen. Ich habe den Wert SynchronizationContext.Current vor der Eingabe auf den Anfragethread gespeichert und ihn im Worker-Thread über SynchronizationContext.SetSynchronizationContext kurz vor dem Aufruf SetResult manuell angewendet. Das hat den Hang gelöst und ich kann nun alle meine asynchronen Methoden abwarten, ohne ConfigureAwait(false) angeben zu müssen.

Meine Frage ist: Ist das ein vernünftiger und korrekter Ansatz zur manuellen Erfassung und Anwendung der SynchronizationContext? FWIW, ich habe versucht, eine einfache Post() mit dem SetResult Delegierten zuerst, aber das verursachte noch einen Hang. Ich bin hier offensichtlich ein wenig außerhalb meiner Komfortzone ... Bitte hilf mir zu verstehen, was vor sich geht!

Antwort

0

Zu meiner Verlegenheit, hatte ich völlig übersehen, dass meine HTTP-Handler aus einer kleinen Basisklasse abgeleitet wurden, die IAsyncHttpHandler in eine sehr fragwürdigen Art und Weise, um die Unterstützung hinzuzufügen, für Asynchron-Handler implementiert:

public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) 
{ 
    ... 
    var task = HandleRequestAsync(...); 

    Task.Run(async() => { await task; }).GetAwaiter().GetResult(); 
    ... 
} 

ich kann Ich erinnere mich nicht mal, warum ich das überhaupt gemacht habe (es war vor über einem Jahr), aber es war definitiv DER dumme Teil, nach dem ich in den letzten paar Tagen gesucht habe!

Ändern der Handler-Basisklasse zu .NET 4.6 HttpTaskAsyncHandler wurde die Hänge los. Entschuldigung für die Verschwendung aller Zeit! :(

3

SetResult ist nicht garantiert, irgendetwas zu rufen. Daher ist dies nicht zuverlässig.

Sie müssen den Synchronisierungskontext an dem Punkt ändern, an dem er erfasst wird. Ein üblicher Schwachpunkt ist hier WebClient, der den Kontext beim Starten einer Webanfrage erfasst. So würde Ihr Code wie folgt aussehen:

SetContext(newContext); 
new WebClient().DownloadAsync(...); 
SetContext(oldContext); 

Stellen Sie den alten Kontext wieder her, um nichts zu stören.

Mit anderen Worten ist das Problem in der Fortsetzungscode, nicht in der Code-Aufruf SetResult.

+0

Guten Punkt über die Wiederherstellung des Kontexts! In Bezug auf Garantien von SetResult (oder deren Fehlen) ... Könnten Sie bitte etwas erweitern? Der * beobachtete * Effekt ist, dass der manuell angewendete Kontext vom Worker-Thread zurückgeleitet wird zu der Fortsetzung ganz gut, so muss es von der SetResult, AFAIU getan werden. Wenn das nur ein Zufall ist, wie Sie angedeutet haben, würde ich gerne mehr über die düsteren Details wissen! – aoven

+0

Die TPL hat ein paar Punkte, wo Code ist Inlineed für Effizienz.Dies ist schrecklich.Siehe https://github.com/dotnet/corefx/issues/2454.Wenn Sie Thread lokalen Status ändern und diese Aufrufkette haben: A => SetResult => B dann B wird diesen Zustand zu sehen Wenn die Kette A => SetResult => QueueToThreadPool (B) ist, wird B den sauberen Zustand sehen. Erklärt das, was Sie sehen? = – usr

+0

Ich kann nicht so oder so etwas sagen ...Meine Kette ist eigentlich A => QueueToThreadPool => SetResult => B und ich habe jetzt festgestellt, dass SetResult die Fortsetzung nicht direkt aufruft, weil der Aufruf erfolgreich ist und der Worker-Thread nicht blockiert. Das einzige Ding (das für mich sichtbar ist) ist das Warten auf A. Das Übergeben von RunContinuationsAsynchron zu TaskCompletionSource scheint keine Wirkung zu haben, gleich mit dem Aufruf von Post/Send. Sie erwähnten bereits das Problem ist in der Fortsetzung ... Welche Art von Problem könnte es möglicherweise, wenn es nie einmal ausgeführt wird (der Block tritt auf der Warte Zeile)? – aoven

Verwandte Themen