Ich bin dabei, eine Bibliothek mit einer API-Oberfläche zu aktualisieren, die in .NET 3.5 erstellt wurde. Infolgedessen sind alle Methoden synchron. Ich kann die API nicht ändern (dh Rückgabewerte in Task konvertieren), da dies die Änderung aller Aufrufer erfordern würde. Ich habe also die Möglichkeit, asynchrone Methoden am besten synchron aufzurufen. Dies steht im Zusammenhang mit ASP.NET 4-, ASP.NET Core- und .NET / .NET Core-Konsolenanwendungen.
Ich war möglicherweise nicht klar genug - die Situation ist, dass ich vorhandenen Code habe, der nicht asynchron ist, und ich möchte neue Bibliotheken wie System.Net.Http und das AWS SDK verwenden, die nur asynchrone Methoden unterstützen. Ich muss also die Lücke schließen und in der Lage sein, Code zu haben, der synchron aufgerufen werden kann, aber dann an anderer Stelle asynchrone Methoden aufrufen kann.
Ich habe viel gelesen und es wurde mehrmals gefragt und beantwortet.
Aufrufen der asynchronen Methode von der nicht asynchronen Methode
Synchrones Warten auf eine asynchrone Operation und warum friert Wait () das Programm hier ein
Aufrufen einer asynchronen Methode von einer synchronen Methode
Wie würde ich eine asynchrone Task <T> -Methode synchron ausführen?
Asynchrone Methode synchron aufrufen
Wie rufe ich eine asynchrone Methode von einer synchronen Methode in C # auf?
Das Problem ist, dass die meisten Antworten unterschiedlich sind! Der häufigste Ansatz, den ich gesehen habe, ist die Verwendung von .Result, aber dies kann zum Stillstand kommen. Ich habe alles Folgende ausprobiert und sie funktionieren, aber ich bin mir nicht sicher, welcher Ansatz der beste ist, um Deadlocks zu vermeiden, eine gute Leistung zu erzielen und gut mit der Laufzeit zu spielen (in Bezug auf die Einhaltung von Aufgabenplanern, Optionen zur Aufgabenerstellung usw.) ). Gibt es eine endgültige Antwort? Was ist der beste Ansatz?
private static T taskSyncRunner<T>(Func<Task<T>> task)
{
T result;
// approach 1
result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult();
// approach 2
result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult();
// approach 3
result = task().ConfigureAwait(false).GetAwaiter().GetResult();
// approach 4
result = Task.Run(task).Result;
// approach 5
result = Task.Run(task).GetAwaiter().GetResult();
// approach 6
var t = task();
t.RunSynchronously();
result = t.Result;
// approach 7
var t1 = task();
Task.WaitAll(t1);
result = t1.Result;
// approach 8?
return result;
}
Antworten:
Erstens ist dies in Ordnung. Ich sage dies, weil es bei Stack Overflow üblich ist, dies als eine Tat des Teufels als pauschale Aussage ohne Rücksicht auf den konkreten Fall herauszustellen.
Es ist nicht erforderlich, dass Sie für die Richtigkeit vollständig asynchron sind . Das Blockieren von etwas Asynchronem, um es zu synchronisieren, hat Leistungskosten, die wichtig oder völlig irrelevant sein können. Es kommt auf den konkreten Fall an.
Deadlocks kommen von zwei Threads, die gleichzeitig versuchen, in denselben Single-Thread-Synchronisationskontext zu gelangen. Jede Technik, die dies vermeidet, vermeidet zuverlässig Deadlocks, die durch Blockieren verursacht werden.
Hier sind alle Ihre Anrufe
.ConfigureAwait(false)
sinnlos, weil Sie nicht warten.RunSynchronously
ist ungültig, da nicht alle Aufgaben auf diese Weise verarbeitet werden können..GetAwaiter().GetResult()
unterscheidet sich davonResult/Wait()
darin, dass es dasawait
Verhalten der Ausnahmeausbreitung nachahmt . Sie müssen entscheiden, ob Sie das wollen oder nicht. (Erforschen Sie also, was dieses Verhalten ist; Sie müssen es hier nicht wiederholen.)Außerdem weisen alle diese Ansätze eine ähnliche Leistung auf. Sie weisen ein Betriebssystemereignis auf die eine oder andere Weise zu und blockieren es. Das ist der teure Teil. Ich weiß nicht, welcher Ansatz absolut am billigsten ist.
Ich persönlich mag das
Task.Run(() => DoSomethingAsync()).Wait();
Muster, weil es Deadlocks kategorisch vermeidet, einfach ist und einige Ausnahmen, die sichGetResult()
möglicherweise verbergen, nicht verbirgt. Aber Sie können auch damit verwendenGetResult()
.quelle
Es gibt keinen universellen "besten" Weg, um das Sync-over-Async-Anti-Pattern durchzuführen. Nur eine Vielzahl von Hacks, die jeweils ihre eigenen Nachteile haben.
Ich empfehle, dass Sie die alten synchronen APIs beibehalten und dann neben ihnen asynchrone APIs einführen. Sie können dies mit dem "boolean argument hack" tun, wie in meinem MSDN-Artikel über Brownfield Async beschrieben .
Zunächst eine kurze Erläuterung der Probleme bei jedem Ansatz in Ihrem Beispiel:
ConfigureAwait
macht nur Sinn, wenn es eine gibtawait
; sonst macht es nichts.Result
wird Ausnahmen in einAggregateException
; Wenn Sie blockieren müssen, verwenden SieGetAwaiter().GetResult()
stattdessen.Task.Run
führt seinen Code in einem Thread-Pool-Thread aus (offensichtlich). Das ist in Ordnung , nur wenn der Code kann auf einem Thread - Pool Thread ausgeführt.RunSynchronously
ist eine erweiterte API, die in äußerst seltenen Situationen bei dynamischer aufgabenbasierter Parallelität verwendet wird. Du bist überhaupt nicht in diesem Szenario.Task.WaitAll
mit einer einzigen Aufgabe ist das gleiche wie geradeWait()
.async () => await x
ist nur eine weniger effiziente Art zu sagen() => x
.Hier ist die Aufschlüsselung:
// Problems (1), (3), (6) result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult(); // Problems (1), (3) result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult(); // Problems (1), (7) result = task().ConfigureAwait(false).GetAwaiter().GetResult(); // Problems (2), (3) result = Task.Run(task).Result; // Problems (3) result = Task.Run(task).GetAwaiter().GetResult(); // Problems (2), (4) var t = task(); t.RunSynchronously(); result = t.Result; // Problems (2), (5) var t1 = task(); Task.WaitAll(t1); result = t1.Result;
Anstelle eines dieser Ansätze sollten Sie, da Sie über vorhandenen, funktionierenden synchronen Code verfügen , diesen zusammen mit dem neueren natürlich asynchronen Code verwenden. Wenn beispielsweise Ihr vorhandener Code verwendet wird
WebClient
:public string Get() { using (var client = new WebClient()) return client.DownloadString(...); }
und Sie möchten eine asynchrone API hinzufügen, dann würde ich es so machen:
private async Task<string> GetCoreAsync(bool sync) { using (var client = new WebClient()) { return sync ? client.DownloadString(...) : await client.DownloadStringTaskAsync(...); } } public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult(); public Task<string> GetAsync() => GetCoreAsync(sync: false);
oder, wenn Sie müssen verwenden
HttpClient
aus irgendeinem Grund:private string GetCoreSync() { using (var client = new WebClient()) return client.DownloadString(...); } private static HttpClient HttpClient { get; } = ...; private async Task<string> GetCoreAsync(bool sync) { return sync ? GetCoreSync() : await HttpClient.GetString(...); } public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult(); public Task<string> GetAsync() => GetCoreAsync(sync: false);
Bei diesem Ansatz würde Ihre Logik in die
Core
Methoden einfließen, die synchron oder asynchron ausgeführt werden können (wie durch densync
Parameter bestimmt). Wennsync
isttrue
, dann sind die Kernmethoden müssen zurückgeben eine bereits erledigte Aufgabe. Verwenden Sie für die Implementierung synchrone APIs, um synchron ausgeführt zu werden, und verwenden Sie asynchrone APIs, um asynchron auszuführen.Schließlich empfehle ich, die synchronen APIs zu verwerfen.
quelle
await
das Ergebnis einer Methode verwenden, weil sie zurückkehrtTask
, nicht weil sie es istasync
. Dies bedeutet, dass Sie für einfache Methoden die Schlüsselwörter löschen können .WebClient
. Aber welchen Nutzen hat die Core-Methode bei der VerwendungHttpClient
? Wäre es nicht sauberer und effizienter, wenn die synchrone Methode die Methode direkt aufruftGetCoreSync
und das boolesche Argument in diesem Fall ausfällt?Genau das habe ich gerade mit dem AWS S3 SDK durchgemacht. Früher war es synchron und ich habe eine Menge Code darauf aufgebaut, aber jetzt ist es asynchron. Und das ist in Ordnung: Sie haben es geändert, nichts, was man durch Stöhnen gewinnen könnte, mach weiter.
Daher muss ich meine App aktualisieren. Ich kann entweder einen großen Teil meiner App so umgestalten, dass sie asynchron ist, oder die asynchrone S3-API "hacken", um sich wie eine Synchronisierung zu verhalten.
Ich werde mich irgendwann mit dem größeren asynchronen Refactoring befassen - es gibt viele Vorteile -, aber für heute habe ich größere Fische zum Braten, also habe ich mich entschieden, die Synchronisierung zu fälschen.
Ursprünglicher Synchronisationscode war
ListObjectsResponse response = api.ListObjects(request);
und ein wirklich einfaches asynchrones Äquivalent , das für mich funktioniert, ist
Task<ListObjectsV2Response> task = api.ListObjectsV2Async(rq2);
ListObjectsV2Response rsp2 = task.GetAwaiter().GetResult();
Ich verstehe zwar, dass Puristen mich dafür anprangern könnten, aber die Realität ist, dass dies nur eines von vielen dringenden Problemen ist und ich endlich Zeit habe, also muss ich Kompromisse eingehen. Perfekt? Funktioniert? Ja.
quelle
Sie können die asynchrone Methode von der nicht asynchronen Methode aus aufrufen. Überprüfen Sie den folgenden Code.
public ActionResult Test() { TestClass result = Task.Run(async () => await GetNumbers()).GetAwaiter().GetResult(); return PartialView(result); } public async Task<TestClass> GetNumbers() { TestClass obj = new TestClass(); HttpResponseMessage response = await APICallHelper.GetData(Functions.API_Call_Url.GetCommonNumbers); if (response.IsSuccessStatusCode) { var result = response.Content.ReadAsStringAsync().Result; obj = JsonConvert.DeserializeObject<TestClass>(result); } return obj; }
quelle