it-swarm.com.de

Entspricht das C # async / Task-Konstrukt Javas Executor / Future?

Ich bin ein langer Entwickler Java Entwickler, aber mit so wenig Verkehr auf SE beschränke ich meine Anzeige nicht auf einzelne Tags. Ich habe festgestellt, dass C # -Fragen mit async/await kommen viel, und soweit ich gelesen habe, ist es der standardmäßige (aber etwas neuere) asynchrone Programmiermechanismus in C #, der ihn zu Javas Executor, Future oder vielleicht genauer machen würde CompatibleFuture Konstrukte (oder sogar wait()/notify() für sehr alten Code).

Jetzt scheint async/await ein praktisches Werkzeug zu sein, schnell zu schreiben und leicht zu verstehen, aber die Fragen geben mir das Gefühl, dass die Leute versuchen, alles zu machen asynchron, und dass dies nicht einmal eine schlechte Sache ist.

In Java erscheint Code, der versucht, die Arbeit so oft wie möglich auf verschiedene Threads zu verlagern, sehr seltsam, und echte Leistungsvorteile ergeben sich aus bestimmten gut durchdachten Stellen, an denen die Arbeit auf verschiedene Threads aufgeteilt ist.

Liegt die Diskrepanz an einem anderen Threading-Modell? Weil C # häufig Desktop-Code mit einer bekannten Plattform ist, während Java ist heutzutage hauptsächlich serverseitig? Oder habe ich gerade ein verzerrtes Bild seiner Relevanz erhalten?

Von https://markheath.net/post/async-antipatterns haben wir:

foreach(var c in customers)
{
    await SendEmailAsync(c);
}

als Beispiel für akzeptablen Code, aber dies scheint darauf hinzudeuten, "alles asynchron zu machen, Sie können immer await zum Synchronisieren verwenden". Liegt es daran, dass await bereits angibt, dass es eigentlich keinen Grund gibt, Threads zu wechseln, während dies in Java der Fall ist

for(Customer c : customer) {
    Future<Result> res = sendEmailAsync(c);
    res.get();
}

würde die Arbeit tatsächlich in einem anderen Thread ausführen, aber get() würde blockieren, also wäre es sequentielle Arbeit, aber mit 2 Threads?

Bei Java besteht die Standardsprache natürlich darin, den Anrufer entscheiden zu lassen, ob etwas asynchron sein muss, anstatt die Implementierung im Voraus entscheiden zu lassen, z.

for(Customer c : customer) {
    CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> sendEmailSync(c));
    // cf can be further used for chaining, exception handling, etc.
}
7
Kayaman

C # 's Task liegt irgendwo auf halber Strecke zwischen Javas Future und CompletableFuture. Die Eigenschaft Result entspricht dem Aufruf von get(), ContinueWith() erledigt die Aufgaben des massiven Arrays von Fortsetzungsfunktionen in CompletableFuture (einige Task.WhenAny Und Task.WhenAll Da drin). Aber complete(T) hat keine Entsprechung (verwenden Sie ein TaskCompletionSource), noch cancel() (übergeben Sie explizite CancellationTokens).

Javas Executor ist größtenteils in C # versteckt. Es gibt einen globalen Thread-Pool, der einem ForkJoinPool entspricht, der automatisch von Task.Run Verwendet wird. Es gibt TaskScheduler, wenn Sie die Ausführung wirklich steuern möchten, diese aber nur sehr selten verwenden.

async/await hat in Java jedoch keine Entsprechung. Der entscheidende Punkt hier ist, dass await someTasknicht dasselbe ist wie someFuture.get(). Letzteres blockiert den ausführenden Thread , bis die Zukunft abgeschlossen ist.

Ersteres macht etwas ganz anderes. Konzeptionell implementiert es so etwas wie Coroutinen. Wenn die Aufgabe nicht abgeschlossen ist, wird die Ausführung der aktuellen Funktion angehalten, aber gibt den Thread frei , um weiter an anderen Dingen zu arbeiten. Erst wenn die Aufgabe abgeschlossen ist, wird die Ausführung ab dem Punkt await fortgesetzt.

In der Praxis handelt es sich um eine Compiler-Transformation. Der Compiler unterteilt eine async -Funktion in Teile. Wenn Sie die Funktion aufrufen, wird das erste Stück ausgeführt, bis es ein await erreicht. Wenn die Aufgabe erledigt ist, wird einfach das nächste Stück ausgeführt. Andernfalls wird ContinueWith verwendet, um das nächste Stück zu planen, das ausgeführt werden soll, sobald die Aufgabe abgeschlossen ist.

Dies ist eine wichtige Unterscheidung, da dies bedeutet, dass Sie keinen Thread des Pools verschlingen, wenn das, auf das Sie warten, beim Netzwerkzugriff blockiert wird. Stattdessen kann der Thread andere Aufgaben bearbeiten.

(Die obige Beschreibung ist vereinfacht. Der Compiler teilt die Funktion nicht auf, sondern verwandelt sie in eine Zustandsmaschine. Und er verwendet nicht ContinueWith, sondern GetAwaiter().UnsafeOnCompleted.)

Dies bedeutet, dass Sie die Effizienz von Fortsetzungen erhalten, jedoch mit dem praktischen Programmiermodell normaler Funktionen.

11
Sebastian Redl