Ich habe ein public async void Foo()
Methode, die ich von der synchronen Methode aufrufen möchte. Bisher habe ich in der MSDN-Dokumentation nur gesehen, dass asynchrone Methoden über asynchrone Methoden aufgerufen werden, aber mein gesamtes Programm ist nicht mit asynchronen Methoden erstellt.
Ist das überhaupt möglich?
Hier ist ein Beispiel für den Aufruf dieser Methoden von einer asynchronen Methode: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx
Jetzt möchte ich diese asynchronen Methoden von Synchronisationsmethoden aus aufrufen.
c#
async-await
Turm
quelle
quelle
async void Foo()
Methode a nicht zurückgibtTask
, bedeutet dies, dass ein Aufrufer nicht wissen kann, wann sie abgeschlossen ist, sondernTask
stattdessen zurückkehren muss .Antworten:
Die asynchrone Programmierung "wächst" durch die Codebasis. Es wurde mit einem Zombie-Virus verglichen . Die beste Lösung ist, es wachsen zu lassen, aber manchmal ist das nicht möglich.
Ich habe einige Typen in meine Nito.AsyncEx- Bibliothek geschrieben, um mit einer teilweise asynchronen Codebasis umzugehen . Es gibt jedoch keine Lösung, die in jeder Situation funktioniert.
Lösung A.
Wenn Sie eine einfache asynchrone Methode haben, die nicht wieder mit ihrem Kontext synchronisiert werden muss, können Sie Folgendes verwenden
Task.WaitAndUnwrapException
:Sie möchten nicht verwenden
Task.Wait
oderTask.Result
weil sie Ausnahmen einschließenAggregateException
.Diese Lösung ist nur geeignet, wenn
MyAsyncMethod
sie nicht mit ihrem Kontext synchronisiert wird. Mit anderen Worten, jedesawait
InMyAsyncMethod
sollte mit endenConfigureAwait(false)
. Dies bedeutet, dass keine UI-Elemente aktualisiert oder auf den ASP.NET-Anforderungskontext zugegriffen werden kann.Lösung B.
Wenn
MyAsyncMethod
eine Synchronisierung mit dem Kontext erforderlich ist, können Sie möglicherweiseAsyncContext.RunTask
einen verschachtelten Kontext bereitstellen:* Update 14.04.2014: In neueren Versionen der Bibliothek lautet die API wie folgt:
(
Task.Result
In diesem Beispiel ist es in Ordnung, daRunTask
es sich ausbreitetTask
Ausnahmen).Der Grund, den Sie möglicherweise
AsyncContext.RunTask
anstelle von benötigen,Task.WaitAndUnwrapException
ist eine ziemlich subtile Deadlock-Möglichkeit, die unter WinForms / WPF / SL / ASP.NET auftritt:Task
.Task
.async
Methode verwendetawait
ohneConfigureAwait
.Task
kann in dieser Situation nicht abgeschlossen werden, da es erst abgeschlossen wird, wenn dieasync
Methode abgeschlossen ist. Dieasync
Methode kann nicht abgeschlossen werden, da versucht wird, die Fortsetzung auf die zu planenSynchronizationContext
, und WinForms / WPF / SL / ASP.NET lässt die Fortsetzung nicht zu, da die synchrone Methode bereits in diesem Kontext ausgeführt wird.Dies ist ein Grund, warum es eine gute Idee ist, so viel wie möglich
ConfigureAwait(false)
in jederasync
Methode zu verwenden .Lösung C.
AsyncContext.RunTask
funktioniert nicht in jedem Szenario. Wenn dieasync
Methode beispielsweise auf etwas wartet, für dessen Abschluss ein UI-Ereignis erforderlich ist, blockieren Sie auch mit dem verschachtelten Kontext. In diesem Fall können Sie dieasync
Methode im Thread-Pool starten :Diese Lösung erfordert jedoch eine
MyAsyncMethod
, die im Thread-Pool-Kontext funktioniert. Daher kann es keine UI-Elemente aktualisieren oder auf den ASP.NET-Anforderungskontext zugreifen. Und in diesem Fall können Sie auch hinzufügen ,ConfigureAwait(false)
um seineawait
Aussagen und Lösung A. verwendenUpdate, 01.05.2019: Die aktuellen "am wenigsten schlimmsten Praktiken" finden Sie in einem MSDN-Artikel hier .
quelle
WaitAndUnwrapException
ist meine eigene Methode aus meiner AsyncEx-Bibliothek . Die offiziellen .NET-Bibliotheken bieten keine große Hilfe beim Mischen von Synchronisations- und Async-Code (und im Allgemeinen sollten Sie dies nicht tun!). Ich warte auf .NET 4.5 RTW und einen neuen Nicht-XP-Laptop, bevor ich AsyncEx auf 4.5 aktualisiere (ich kann derzeit nicht für 4.5 entwickeln, da ich noch einige Wochen auf XP stecke).AsyncContext
hat jetzt eineRun
Methode, die einen Lambda-Ausdruck nimmt, also sollten Sie verwendenvar result = AsyncContext.Run(() => MyAsyncMethod());
RunTask
Methode zu geben. Das nächste, was ich finden konnte, warRun
, aber das hat keineResult
Eigenschaft.var result = AsyncContext.Run(MyAsyncMethod);
Das Hinzufügen einer Lösung, die mein Problem endlich gelöst hat, spart hoffentlich jemandem Zeit.
Lesen Sie zuerst ein paar Artikel von Stephen Cleary :
Von den "zwei Best Practices" in "Nicht auf asynchronem Code blockieren" hat die erste für mich nicht funktioniert und die zweite war nicht anwendbar (im Grunde genommen, wenn ich sie verwenden kann
await
!).Hier ist meine Problemumgehung: Wickeln Sie den Anruf in einen
Task.Run<>(async () => await FunctionAsync());
und hoffentlich keinen Deadlock mehr.Hier ist mein Code:
quelle
Task.Run()
in einem asynchronen Code keine bewährte Methode ist. Aber wie lautet die Antwort auf die ursprüngliche Frage? Nie eine asynchrone Methode synchron aufrufen? Wir wünschen, aber in einer realen Welt müssen wir manchmal.Parallel.ForEach
Missbrauch in der "realen Welt" keine Auswirkungen hat und schließlich die Server heruntergefahren hat. Dieser Code ist für Konsolen-Apps in Ordnung, sollte jedoch, wie @ChrisPratt sagt, nicht in Web-Apps verwendet werden. Es könnte "jetzt" funktionieren, ist aber nicht skalierbar.Microsoft hat eine (interne) AsyncHelper-Klasse erstellt, um Async als Sync auszuführen. Die Quelle sieht aus wie:
Die Microsoft.AspNet.Identity-Basisklassen verfügen nur über Async-Methoden. Um sie als Sync aufzurufen, gibt es Klassen mit Erweiterungsmethoden, die wie folgt aussehen (Beispielverwendung):
Für diejenigen, die sich Gedanken über die Lizenzierungsbedingungen für Code machen, ist hier ein Link zu sehr ähnlichem Code (fügt nur Unterstützung für die Kultur im Thread hinzu), der Kommentare enthält, die darauf hinweisen, dass es sich um eine von Microsoft lizenzierte MIT handelt. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
quelle
await
Anrufe mitConfigureAwait(false)
. Ich habe versuchtAsyncHelper.RunSync
, eine asynchrone Funktion aus derApplication_Start()
Funktion in Global.asax aufzurufen, und es scheint zu funktionieren. Bedeutet dies, dass diesAsyncHelper.RunSync
nicht anfällig für das Deadlock-Problem "Marschall zurück zum Kontext des Anrufers" ist, über das ich an anderer Stelle in diesem Beitrag gelesen habe?async Main ist jetzt Teil von C # 7.2 und kann in den erweiterten Build-Einstellungen des Projekts aktiviert werden.
Für C # <7.2 lautet der richtige Weg:
Sie werden dies in vielen Microsoft-Dokumentationen sehen, zum Beispiel: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- Themen-Abonnements
quelle
MainAsync().Wait()
?Sie lesen das Schlüsselwort 'await' als "Diese lange laufende Aufgabe starten und dann die Steuerung an die aufrufende Methode zurückgeben". Sobald die lang laufende Aufgabe erledigt ist, führt sie den Code danach aus. Der Code nach dem Warten ähnelt den früheren CallBack-Methoden. Der große Unterschied besteht darin, dass der logische Ablauf nicht unterbrochen wird, was das Schreiben und Lesen erheblich erleichtert.
quelle
Wait
schließt Ausnahmen aus und hat die Möglichkeit eines Deadlocks.await
würden, würde sie synchron ausgeführt. Zumindest funktioniert das bei mir (ohne anzurufenmyTask.Wait
). Eigentlich habe ich eine Ausnahme bekommen, als ich versucht habe anzurufen,myTask.RunSynchronously()
weil es bereits ausgeführt wurde!.Result
.Result
Zeitpunkt durch den Aufruf blockiert wird , sodass er nie dort ankommt. UndResult
endet nie, weil es auf jemanden wartet, derResult
auf das Ende wartet , im Grunde: DIch bin nicht 100% sicher, aber ich glaube, dass die in diesem Blog beschriebene Technik unter vielen Umständen funktionieren sollte:
quelle
Es gibt jedoch eine gute Lösung, die in jeder Situation funktioniert (fast: siehe Kommentare): eine Ad-hoc-Nachrichtenpumpe (SynchronizationContext).
Der aufrufende Thread wird wie erwartet blockiert, wobei weiterhin sichergestellt wird, dass alle von der asynchronen Funktion aufgerufenen Fortsetzungen nicht blockieren, da sie für den Ad-hoc-SynchronizationContext (Nachrichtenpumpe) gemarshallt werden, der auf dem aufrufenden Thread ausgeführt wird.
Der Code des Ad-hoc-Nachrichtenpumpen-Helfers:
Verwendungszweck:
Eine detailliertere Beschreibung der Async-Pumpe finden Sie hier .
quelle
Für alle, die sich noch mehr mit dieser Frage beschäftigen ...
Wenn Sie hineinschauen,
Microsoft.VisualStudio.Services.WebApi
gibt es eine Klasse namensTaskExtensions
. Innerhalb dieser Klasse sehen Sie die statische ErweiterungsmethodeTask.SyncResult()
, die den Thread wie vollständig blockiert, bis die Aufgabe zurückkehrt.Intern ruft es auf,
task.GetAwaiter().GetResult()
was ziemlich einfach ist, aber es ist überladen, an jederasync
Methode zu arbeiten, die zurückkehrtTask
,Task<T>
oderTask<HttpResponseMessage>
... syntaktischer Zucker, Baby ... Papa hat einen süßen Zahn.Es sieht so aus,
...GetAwaiter().GetResult()
als wäre dies die offizielle Methode von MS, um asynchronen Code in einem blockierenden Kontext auszuführen. Scheint für meinen Anwendungsfall sehr gut zu funktionieren.quelle
Oder benutze dies:
quelle
Sie können jede asynchrone Methode aus synchronem Code aufrufen, dh bis Sie sie benötigen
await
. In diesem Fall müssen sie auch markiertasync
werden.Wie viele Leute hier vorschlagen, können Sie Wait () oder Result für die resultierende Aufgabe in Ihrer synchronen Methode aufrufen, aber dann erhalten Sie einen blockierenden Aufruf in dieser Methode, der den Zweck der Asynchronisierung zunichte macht.
Wenn Sie Ihre Methode wirklich nicht erstellen können
async
und die synchrone Methode nicht blockieren möchten, müssen Sie eine Rückrufmethode verwenden, indem Sie sie als Parameter an die ContinueWith-Methode für die Aufgabe übergeben.quelle
async
werden" lenkte meine Aufmerksamkeit von dem ab, was Sie wirklich sagten.Ich weiß, dass ich so spät bin. Aber für den Fall, dass jemand wie ich dies auf eine ordentliche, einfache Art und Weise lösen wollte, ohne von einer anderen Bibliothek abhängig zu sein.
Ich habe den folgenden Code von Ryan gefunden
dann kannst du es so nennen
quelle
Nachdem ich stundenlang verschiedene Methoden ausprobiert hatte, mit mehr oder weniger Erfolg, endete ich damit. Es endet nicht in einem Deadlock, während das Ergebnis abgerufen wird, und es wird auch die ursprüngliche Ausnahme und nicht die umschlossene Ausnahme abgerufen und ausgelöst.
quelle
Es könnte von einem neuen Thread aufgerufen werden (NICHT vom Thread-Pool!):
quelle
Diese asynchronen Windows-Methoden haben eine raffinierte kleine Methode namens AsTask (). Sie können dies verwenden, damit sich die Methode selbst als Task zurückgibt, sodass Sie Wait () manuell aufrufen können.
In einer Windows Phone 8 Silverlight-Anwendung können Sie beispielsweise Folgendes tun:
Hoffe das hilft!
quelle
Wenn Sie es ausführen möchten, synchronisieren Sie es
quelle
RunSynchronously()
einer heißen Aufgabe führt zu einemInvalidOperationException
. Versuchen Sie es mit diesem Code:Task.Run(() => {}).RunSynchronously();
quelle