Ich arbeite an einem Projekt, das sowohl die asynchrone als auch die synchrone Version derselben Logik / Methode unterstützen muss. So muss ich zum Beispiel haben:
public class Foo
{
public bool IsIt()
{
using (var conn = new SqlConnection(DB.ConnString))
{
return conn.Query<bool>("SELECT IsIt FROM SomeTable");
}
}
public async Task<bool> IsItAsync()
{
using (var conn = new SqlConnection(DB.ConnString))
{
return await conn.QueryAsync<bool>("SELECT IsIt FROM SomeTable");
}
}
}
Die Async- und Sync-Logik für diese Methoden ist in jeder Hinsicht identisch, außer dass eine asynchron ist und eine andere nicht. Gibt es einen legitimen Weg, um in einem solchen Szenario eine Verletzung des DRY-Prinzips zu vermeiden? Ich habe gesehen, dass Leute sagen, Sie könnten GetAwaiter (). GetResult () für eine asynchrone Methode verwenden und sie von Ihrer Synchronisierungsmethode aus aufrufen? Ist dieser Thread in allen Szenarien sicher? Gibt es einen anderen, besseren Weg, dies zu tun, oder bin ich gezwungen, die Logik zu duplizieren?
c#
.net
asynchronous
Marko
quelle
quelle
return Task.FromResult(IsIt());
)Antworten:
Sie haben in Ihrer Frage mehrere Fragen gestellt. Ich werde sie etwas anders aufschlüsseln als Sie. Aber lassen Sie mich zuerst die Frage direkt beantworten.
Wir alle wollen eine Kamera, die leicht, hochwertig und billig ist, aber wie das Sprichwort sagt, können Sie höchstens zwei von diesen drei bekommen. Sie sind hier in der gleichen Situation. Sie möchten eine Lösung, die effizient und sicher ist und Code zwischen synchronen und asynchronen Pfaden teilt. Sie werden nur zwei davon bekommen.
Lassen Sie mich zusammenfassen, warum das so ist. Wir beginnen mit dieser Frage:
Der Punkt dieser Frage lautet: "Kann ich die synchronen und asynchronen Pfade gemeinsam nutzen, indem der synchrone Pfad einfach synchron auf die asynchrone Version wartet?"
Lassen Sie mich in diesem Punkt ganz klar sein, denn es ist wichtig:
Sie sollten sofort aufhören, Ratschläge von diesen Menschen zu nehmen .
Das ist ein extrem schlechter Rat. Es ist sehr gefährlich, ein Ergebnis einer asynchronen Aufgabe synchron abzurufen, es sei denn, Sie haben Beweise dafür, dass die Aufgabe normal oder abnormal abgeschlossen wurde .
Der Grund, warum dies ein extrem schlechter Rat ist, ist, dieses Szenario zu betrachten. Sie möchten den Rasen mähen, aber Ihr Rasenmähermesser ist gebrochen. Sie entscheiden sich für diesen Workflow:
Was geschieht? Sie schlafen für immer, weil das Überprüfen der E-Mails jetzt auf etwas beschränkt ist, das nach dem Eintreffen der E-Mails geschieht .
Es ist sehr einfach , in diese Situation zu geraten, wenn Sie synchron auf eine beliebige Aufgabe warten. Für diese Aufgabe ist möglicherweise die Arbeit des Threads geplant, der jetzt wartet , und jetzt wird diese Zukunft niemals eintreffen, weil Sie darauf warten.
Wenn Sie asynchron warten, ist alles in Ordnung! Sie überprüfen regelmäßig die Post und während Sie warten, machen Sie ein Sandwich oder machen Ihre Steuern oder was auch immer; Sie erledigen Ihre Arbeit, während Sie warten.
Warten Sie niemals synchron. Wenn die Aufgabe erledigt ist, ist sie nicht erforderlich . Wenn die Aufgabe nicht erledigt ist, aber geplant ist, dass sie vom aktuellen Thread ausgeführt wird, ist sie ineffizient, da der aktuelle Thread möglicherweise andere Arbeiten erledigt, anstatt zu warten. Wenn die Aufgabe nicht erledigt ist und die Ausführung des aktuellen Threads geplant ist, bleibt sie hängen, um synchron zu warten. Es gibt keinen guten Grund, erneut synchron zu warten, es sei denn, Sie wissen bereits, dass die Aufgabe abgeschlossen ist .
Weitere Informationen zu diesem Thema finden Sie unter
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
Stephen erklärt das reale Szenario viel besser als ich.
Betrachten wir nun die "andere Richtung". Können wir Code gemeinsam nutzen, indem die asynchrone Version einfach die synchrone Version in einem Arbeitsthread ausführt?
Das ist aus den folgenden Gründen möglicherweise und in der Tat wahrscheinlich eine schlechte Idee.
Es ist ineffizient, wenn der synchrone Betrieb E / A-Arbeit mit hoher Latenz ist. Dies stellt im Wesentlichen einen Arbeiter ein und lässt diesen schlafen, bis eine Aufgabe erledigt ist. Fäden sind wahnsinnig teuer . Sie verbrauchen standardmäßig mindestens eine Million Byte Adressraum, benötigen Zeit und Betriebssystemressourcen. Sie möchten keinen Thread brennen, der nutzlose Arbeit leistet.
Die synchrone Operation ist möglicherweise nicht threadsicher geschrieben.
Dies ist eine vernünftigere Technik, wenn die Arbeit mit hoher Latenz prozessorgebunden ist, aber wenn dies der Fall ist, möchten Sie sie wahrscheinlich nicht einfach an einen Arbeitsthread übergeben. Sie möchten wahrscheinlich die Task-Parallelbibliothek verwenden, um sie auf so viele CPUs wie möglich zu parallelisieren, Sie möchten wahrscheinlich eine Abbruchlogik und Sie können die synchrone Version nicht einfach dazu bringen, all das zu tun, da es sich dann bereits um die asynchrone Version handelt .
Weiterführende Literatur; wieder erklärt Stephen es sehr deutlich:
Warum nicht Task.Run verwenden:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html
Weitere "Do and Don't" -Szenarien für Task.Run:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html
Was lässt uns das dann? Beide Techniken zum Teilen von Code führen entweder zu Deadlocks oder zu großen Ineffizienzen. Wir kommen zu dem Schluss, dass Sie eine Wahl treffen müssen. Möchten Sie ein Programm, das effizient und korrekt ist und den Anrufer begeistert, oder möchten Sie einige Tastenanschläge speichern, die durch das Duplizieren einer kleinen Codemenge zwischen dem synchronen und dem asynchronen Pfad entstehen? Ich fürchte, Sie bekommen nicht beides.
quelle
Task.Run(() => SynchronousMethod())
in die asynchrone Version nicht das ist, was das OP will?Dns.GetHostEntryAsync
in .NET undFileStream.ReadAsync
für bestimmte Dateitypen implementiert . Das Betriebssystem bietet einfach keine Asynchron - Schnittstelle, so dass die Laufzeit zu fälschen hat (und es ist nicht die Sprache spezifisch sagen wir, Erlang Laufzeit läuft gesamten Baum der Worker - Prozesse , mit mehreren Threads innerhalb eines jeden, nicht blockierende Platten - I / O und den Namen zur Verfügung zu stellen Auflösung).Es ist schwierig, eine einheitliche Antwort auf diese Frage zu geben. Leider gibt es keine einfache und perfekte Möglichkeit, zwischen asynchronem und synchronem Code wiederzuverwenden. Aber hier sind einige Prinzipien zu beachten:
Query()
eine undQueryAsync()
die andere aufgerufen) oder Verbindungen mit unterschiedlichen Einstellungen hergestellt. Selbst wenn es strukturell ähnlich ist, gibt es oft genug Verhaltensunterschiede, um als separater Code mit unterschiedlichen Anforderungen behandelt zu werden. Beachten Sie die Unterschiede zwischen Async- und Sync- Implementierungen von Methoden in der File-Klasse, zum Beispiel: Es werden keine Anstrengungen unternommen, um sie dazu zu bringen, denselben Code zu verwendenTask.FromResult(...)
.Viel Glück.
quelle
Einfach; Lassen Sie den Synchronen den Asynchronen aufrufen. Es gibt sogar eine praktische Methode
Task<T>
, um genau das zu tun:quelle