Ich versuche, Nebenläufigkeit zu verstehen, indem ich es im Code mache. Ich habe ein Code-Snippet, von dem ich dachte, dass es asynchron läuft. Aber als ich die Debug-Writerline-Anweisungen eingab, stellte ich fest, dass es synchron lief. Kann jemand erklären, was ich anders machen muss, um ComputeBB () mit Task.Something auf einen anderen Thread zu schieben?
Erläuterung Ich möchte, dass dieser Code ComputeBB in einem anderen Thread ausführt, damit der Hauptthread ohne Blockierung weiterläuft.
Hier ist der Code:
%Vor%Hier ist die Ausgabe im unmittelbaren Fenster:
%Vor%Erläuterung Wenn es wirklich asynchron ausgeführt würde, würde dies in dieser Reihenfolge geschehen:
%Vor%Aber es ist nicht.
Ausarbeitung Der aufrufende Code hat eine Signatur wie folgt: private static async Task loadAsBinaryAsync (string fileName) Auf der nächsten Ebene versuche ich jedoch, die Verwendung von async zu beenden. Also hier ist der Call-Stack von oben nach unten:
%Vor%Ich habe ein Code-Snippet, von dem ich annahm, dass es asynchron lief. Aber als ich die Debug-Writerline-Anweisungen eingab, stellte ich fest, dass sie synchron ablaufen.
await
wird zum asynchron Warten eines abgeschlossenen Vorgangs verwendet. Währenddessen gibt es die Kontrolle zurück an die aufrufende Methode bis zum Abschluss.
was ich anders machen muss, um ComputeBB () auf einen anderen Thread zu schieben
Es wurde bereits in einem Threadpool-Thread ausgeführt. Wenn Sie nicht asynchron auf "fire and forget" warten möchten, nicht await
den Ausdruck. Beachten Sie, dass dies Auswirkungen auf die Ausnahmebehandlung hat. Jede Ausnahme, die innerhalb des angegebenen Delegaten auftritt, wird innerhalb des angegebenen Task
festgehalten, wenn Sie nicht await
haben, besteht die Chance, dass sie unbehandelt wird.
Bearbeiten:
Sehen wir uns diesen Code an:
%Vor% Was Sie gerade tun, blockiert synchron, wenn Sie tsk.Result
verwenden. Aus irgendeinem Grund rufen Sie Task.Run
zweimal an, einmal in jeder Methode. Das ist unnötig. Wenn du deine ptsDTM
-Instanz von CreateFromExistingFile
zurückgeben willst, musst du await
it, es gibt kein Problem damit. Die Ausführung "Feuer und Vergessen" interessiert das Ergebnis überhaupt nicht. Es möchte einfach starten, welche Operation es benötigt, wenn es fehlschlägt oder erfolgreich ist, ist es normalerweise kein Problem. Das ist hier eindeutig nicht der Fall.
Sie müssen so etwas tun:
%Vor%Und dann brauchen Sie% cc_de% nicht weiter oben in der Aufrufliste, sondern einfach:
%Vor%Bei Bedarf.
Lesen Sie auch die C # -Namenskonventionen , dem Sie derzeit nicht folgen.
await
besteht darin, die Synchronität im asynchronen Code wiederherzustellen. Dadurch können Sie die Teile, die synchron und asynchron ausgeführt werden, problemlos partitionieren. Ihr Beispiel ist insofern absurd, als es niemals irgendeinen Vorteil daraus zieht - wenn Sie die Methode gerade direkt aufgerufen haben, anstatt sie in Task.Run
und await
ging zu verpacken, hätten Sie genau das gleiche Ergebnis (mit weniger Overhead). .
Bedenken Sie dies jedoch:
%Vor% Auch hier haben Sie die Synchronizität zurück ( await
fungiert als Synchronisationsbarriere), aber Sie haben tatsächlich drei unabhängige Operationen asynchron zueinander ausgeführt .
Nun, es gibt keinen Grund, so etwas in Ihrem Code zu tun, da Sie Parallel.ForEach
auf der untersten Ebene verwenden - Sie verwenden bereits die CPU bis zum Maximum (mit unnötigem Overhead, aber ignorieren wir das für jetzt).
Also ist die grundlegende Verwendung von await
eigentlich eher asynchrone I / O als die CPU-Arbeit - abgesehen von der Vereinfachung von Code, der darauf beruht, dass einige Teile der CPU-Arbeit synchronisiert werden und einige nicht (z Sie haben vier Threads der Ausführung, die gleichzeitig verschiedene Teile des Problems bearbeiten, aber irgendwann müssen sie wiedervereinigt werden, um die einzelnen Teile zu verstehen - schauen Sie sich beispielsweise die Klasse Barrier
an. Dazu gehören Dinge wie "Sicherstellen, dass die Benutzeroberfläche nicht blockiert, während einige CPU-intensive Vorgänge im Hintergrund ablaufen" - dies führt dazu, dass die CPU in Bezug auf die Benutzeroberfläche asynchron arbeitet . Aber irgendwann möchten Sie die Synchronität wieder einführen, um sicherzustellen, dass Sie die Ergebnisse der Arbeit auf der Benutzeroberfläche anzeigen können.
Betrachten Sie dieses winforms-Code-Snippet:
%Vor% (Beachten Sie, dass die Methode async void
, nicht async Task
oder async Task<T>
) ist
Was passiert, ist, dass (auf dem GUI-Thread) der Beschriftung zuerst der Text Calculating...
zugewiesen wird, dann die asynchrone DoTheUltraHardStuff
-Methode geplant wird und dann die Methode zurückkehrt. Sofort. Dadurch kann der GUI-Thread alles tun, was er tun muss. Wie auch immer - sobald der asynchrone Task abgeschlossen ist und die GUI den Callback frei hat, wird die Ausführung von btnDoStuff_Click
fortgesetzt mit dem result
bereits vergeben ( (oder natürlich eine Ausnahme), zurück auf den GUI-Thread, so dass Sie die Bezeichnung auf den neuen Text einschließlich des Ergebnisses der asynchronen Operation setzen können.
Asynchronität ist nicht eine absolute Eigenschaft - das Zeug ist asynchron zu etwas anderem und synchron zu etwas anderem. Es macht nur Sinn in Bezug auf etwas anderes.
Hoffentlich können Sie jetzt zu Ihrem ursprünglichen Code zurückkehren und den Teil verstehen, den Sie vorher falsch verstanden haben. Die Lösungen sind natürlich mehrere, aber sie hängen sehr davon ab, wie und warum Sie versuchen, das zu tun, was Sie tun möchten. Ich vermute, dass du Task.Run
oder await
überhaupt nicht benutzen musst - die Parallel.ForEach
versucht bereits, die CPU-Arbeit über mehrere CPU-Kerne zu verteilen, und das Einzige, was du tun kannst, ist sicherzustellen, dass anderer Code nicht funktioniert Ich muss nicht darauf warten, dass diese Arbeit beendet wird - was in einer GUI-Anwendung sehr sinnvoll wäre, aber ich sehe nicht, wie es in einer Konsolenanwendung mit dem einzigartigen Zweck der Berechnung dieses einzelnen Dinges nützlich wäre.
Also können Sie tatsächlich await
für Fire-and-Forget-Code verwenden - aber nur als Teil von Code, der den gewünschten Code nicht verhindert von der Ausführung fortfahren. Zum Beispiel könnten Sie Code wie folgt haben:
Dies erlaubt SomeHardWorkAsync
asynchron in Bezug auf DoSomeOtherWorkInTheMeantime
, aber nicht in await result
auszuführen. Und natürlich können Sie await
s in SomeHardWorkAsync
verwenden, ohne die Asynchronität zwischen SomeHardWorkAsync
und DoSomeOtherWorkInTheMeantime
zu verringern.
Das GUI-Beispiel, das ich oben gezeigt habe, nutzt den Vorteil, die Fortsetzung als etwas zu behandeln, das nach dem Abschluss der Aufgabe passiert, während die in der Methode Task
erstellte async
ignoriert wird (es gibt wirklich nicht viel von a Unterschied zwischen der Verwendung von async void
und async Task
, wenn Sie das Ergebnis ignorieren . Um beispielsweise Ihre Methode zu aktivieren und zu löschen, können Sie einen Code wie diesen verwenden:
Dies führt dazu, dass DoStuffWithResult
ausgeführt wird, sobald result
bereit ist, während die Methode Fire
selbst unmittelbar nach Ausführung von ProcessFileAsync
(bis zum ersten await
oder jedem expliziten return someTask
) zurückkehrt. .
Dieses Muster ist normalerweise verpönt - es gibt wirklich keinen Grund, void
aus einer asynchronen Methode zurückzugeben (abgesehen von Event-Handlern); Sie könnten genauso einfach Task
(oder sogar Task<T>
abhängig vom Szenario) zurückgeben und den Anrufer entscheiden lassen, ob er möchte, dass sein Code synchron zu Ihnen ausgeführt wird oder nicht.
Wieder,
%Vor% macht dasselbe wie async void
, außer dass der Aufrufer entscheiden kann, was er mit der asynchronen Task machen soll. Vielleicht möchte er zwei davon parallel starten und weitermachen, nachdem alle fertig sind? Er kann nur await Task.WhenAll(Fire("1"), Fire("2"))
.Oder er will nur, dass das völlig asynchron in Bezug auf seinen Code passiert, also ruft er einfach Fire("1")
an und ignoriert die resultierende Task
(natürlich, idealerweise willst du zumindest mögliche Ausnahmen behandeln).
Tags und Links c# multithreading task-parallel-library async-await task