Ich habe eine mehrschichtige .NET 4.5-Anwendung, die eine Methode mit C #s neuem async
und aufruftawait
Schlüsselwörtern , die nur hängt, und ich kann nicht verstehen, warum.
Unten habe ich eine asynchrone Methode, die unser Datenbankdienstprogramm erweitert OurDBConn
(im Grunde ein Wrapper für den Basiswert DBConnection
und die DBCommand
Objekte):
public static async Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
T result = await Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
return result;
}
Dann habe ich eine asynchrone Methode der mittleren Ebene, die dies aufruft, um einige langsam laufende Summen zu erhalten:
public static async Task<ResultClass> GetTotalAsync( ... )
{
var result = await this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
return result;
}
Endlich habe ich eine UI-Methode (eine MVC-Aktion), die synchron ausgeführt wird:
Task<ResultClass> asyncTask = midLevelClass.GetTotalAsync(...);
// do other stuff that takes a few seconds
ResultClass slowTotal = asyncTask.Result;
Das Problem ist, dass es für immer in dieser letzten Zeile hängt. Es macht das gleiche, wenn ich anrufeasyncTask.Wait()
. Wenn ich die langsame SQL-Methode direkt ausführe, dauert es ungefähr 4 Sekunden.
Das Verhalten, das ich erwarte, ist das, wenn es darum geht asyncTask.Result
fertig ist, wenn es nicht fertig ist, es warten sollte, bis es ist, und sobald es ist, sollte es das Ergebnis zurückgeben.
Wenn ich mit einem Debugger durchkomme, wird die SQL-Anweisung abgeschlossen und die Lambda-Funktion beendet, aber die return result;
Zeile vonGetTotalAsync
wird nie erreicht.
Irgendeine Idee, was ich falsch mache?
Irgendwelche Vorschläge, wo ich nachforschen muss, um dies zu beheben?
Könnte dies irgendwo ein Deadlock sein, und wenn ja, gibt es einen direkten Weg, ihn zu finden?
SynchronizationContext
.async
/ verwendeteawait
.Dies ist das klassische Mixed-
async
Deadlock-Szenario, wie ich es in meinem Blog beschreibe . Jason hat es gut beschrieben: Standardmäßig wird bei jedem ein "Kontext" gespeichertawait
und verwendet, um dieasync
Methode fortzusetzen . Dieser "Kontext" ist der Strom, esSynchronizationContext
sei dennnull
, er ist der StromTaskScheduler
. In diesem Fall ist er der Strom . Wenn dieasync
Methode versucht, fortzufahren, tritt sie zuerst erneut in den erfassten "Kontext" ein (in diesem Fall ein ASP.NETSynchronizationContext
). Das ASP.NETSynchronizationContext
erlaubt jeweils nur einen Thread im Kontext, und es befindet sich bereits ein Thread im Kontext - der Thread ist blockiertTask.Result
.Es gibt zwei Richtlinien, die diesen Deadlock vermeiden:
async
ganzen Weg nach unten. Sie erwähnen, dass Sie dies "nicht" können, aber ich bin mir nicht sicher, warum nicht. ASP.NET MVC unter .NET 4.5 kann sicherlichasync
Aktionen unterstützen, und es ist keine schwierige Änderung.ConfigureAwait(continueOnCapturedContext: false)
so viel wie möglich. Dies überschreibt das Standardverhalten der Wiederaufnahme des erfassten Kontexts.quelle
ConfigureAwait(false)
gewährleistet , dass die aktuelle Funktion auf einem anderen Kontext wieder aufnimmt?async
Aktion wechseln, ohne die Funktionsweise des Clients zu beeinträchtigen. Ich habe jedoch vor, diese Option längerfristig zu untersuchen.ConfigureAwait(false)
des Anrufbaums das Problem des OP gelöst hätte.async
wirkt sich überhaupt nicht auf die Clientseite aus. Ich erkläre dies in einem anderen Blog-Beitrag,async
Ändert das HTTP-Protokoll nicht .async
, durch die Codebasis zu "wachsen". Wenn Ihr Controller - Methode auf asynchrone Operationen abhängen kann, dann ist die Basisklassenmethode sollte zurückkehrenTask<ActionResult>
. Der Übergang zu einem großen Projektasync
ist immer umständlich, da das Mischenasync
und Synchronisieren von Code schwierig und schwierig ist. Reinerasync
Code ist viel einfacher.Ich befand mich in derselben Deadlock-Situation, aber in meinem Fall, als ich eine asynchrone Methode von einer Synchronisierungsmethode aus aufrief, funktionierte für mich Folgendes:
Ist das ein guter Ansatz, eine Idee?
quelle
Nur um die akzeptierte Antwort zu ergänzen (nicht genug Repräsentanten, um sie zu kommentieren), trat dieses Problem beim Blockieren der Verwendung von "
task.Result
event" auf, obwohl alleawait
unten aufgeführten EreignisseConfigureAwait(false)
wie in diesem Beispiel:Das Problem lag tatsächlich beim externen Bibliothekscode. Die asynchrone Bibliotheksmethode hat versucht, im aufrufenden Synchronisierungskontext fortzufahren, unabhängig davon, wie ich das Warten konfiguriert habe, was zu einem Deadlock führte.
Daher bestand die Antwort darin, meine eigene Version des externen Bibliothekscodes zu rollen
ExternalLibraryStringAsync
, damit er die gewünschten Fortsetzungseigenschaften aufweist.falsche Antwort für historische Zwecke
Nach viel Schmerz und Angst fand ich die Lösung in diesem Blog-Beitrag (Strg-f für 'Deadlock') begraben. Es dreht sich um die Verwendung
task.ContinueWith
anstelle des Nacktentask.Result
.Bisheriges Deadlocking-Beispiel:
Vermeiden Sie den Deadlock wie folgt:
quelle
Task
abgeschlossen wurde, und geben dem Aufrufer keine Möglichkeit, festzustellen, wann die Mutation des zurückgegebenen Objekts tatsächlich stattfindet.GetFooSynchronous
Methode packen ?Task
anstatt zu blockieren.schnelle Antwort: Ändern Sie diese Zeile
zu
Warum? Sie sollten .result nicht verwenden, um das Ergebnis von Aufgaben in den meisten Anwendungen außer Konsolenanwendungen abzurufen. Wenn Sie dies tun, bleibt Ihr Programm hängen, wenn es dort ankommt
Sie können auch den folgenden Code ausprobieren, wenn Sie .Result verwenden möchten
quelle