it-swarm.com.de

Was ist der Unterschied zwischen asynchroner Programmierung und Multithreading?

Ich dachte, dass es im Grunde dasselbe ist - Programme zu schreiben, die Aufgaben zwischen Prozessoren aufteilen (auf Maschinen mit 2+ Prozessoren). Dann lese ich https://msdn.Microsoft.com/en-us/library/hh191443.aspx , was besagt

Asynchrone Methoden sollen nicht blockierende Operationen sein. Ein wait-Ausdruck in einer asynchronen Methode blockiert den aktuellen Thread nicht, während die erwartete Task ausgeführt wird. Stattdessen meldet der Ausdruck den Rest der Methode als Fortsetzung an und gibt die Steuerung an den Aufrufer der asynchronen Methode zurück.

Durch die Schlüsselwörter async und await werden keine zusätzlichen Threads erstellt. Asynchrone Methoden erfordern kein Multithreading, da eine asynchrone Methode nicht in einem eigenen Thread ausgeführt wird. Die Methode wird im aktuellen Synchronisationskontext ausgeführt und verwendet die Zeit im Thread nur, wenn die Methode aktiv ist. Sie können Task.Run verwenden, um CPU-gebundene Arbeit in einen Hintergrundthread zu verschieben. Ein Hintergrundthread hilft jedoch nicht bei einem Prozess, der nur darauf wartet, dass Ergebnisse verfügbar werden.

und ich frage mich, ob jemand das für mich ins Englische übersetzen kann. Es scheint einen Unterschied zwischen Asynchronität (ist das ein Wort?) Und Threading zu machen und impliziert, dass Sie ein Programm haben können, das asynchrone Aufgaben hat, aber kein Multithreading.

Jetzt verstehe ich die Idee von asynchronen Tasks wie das Beispiel auf Seite. 467 von Jon Skeets C # In Depth, Third Edition

async void DisplayWebsiteLength ( object sender, EventArgs e )
{
    label.Text = "Fetching ...";
    using ( HttpClient client = new HttpClient() )
    {
        Task<string> task = client.GetStringAsync("http://csharpindepth.com");
        string text = await task;
        label.Text = text.Length.ToString();
    }
}

Das Schlüsselwort async bedeutet " Diese Funktion wird, wann immer sie aufgerufen wird, nicht in einem Kontext aufgerufen, in dem ihre Vervollständigung erforderlich ist, damit nach ihrem Aufruf alles aufgerufen wird."

Mit anderen Worten, schreiben Sie es mitten in einer Aufgabe

int x = 5; 
DisplayWebsiteLength();
double y = Math.Pow((double)x,2000.0);

, da DisplayWebsiteLength() nichts mit x oder y zu tun hat, wird DisplayWebsiteLength() wie "im Hintergrund" ausgeführt

                processor 1                |      processor 2
-------------------------------------------------------------------
int x = 5;                                 |  DisplayWebsiteLength()
double y = Math.Pow((double)x,2000.0);     |

Das ist natürlich ein dummes Beispiel, aber habe ich recht oder bin ich total verwirrt oder was?

(Außerdem bin ich verwirrt darüber, warum sender und e im Hauptteil der obigen Funktion nie verwendet werden.)

155
user5648283

Ihr Missverständnis ist äußerst verbreitet. Vielen Menschen wird beigebracht, dass Multithreading und Asynchronität dasselbe sind, aber nicht.

Eine Analogie hilft normalerweise. Sie kochen in einem Restaurant. Eine Bestellung für Eier und Toast geht ein.

  • Synchron: Sie kochen die Eier, dann kochen Sie den Toast.
  • Asynchron, Single Threaded: Sie starten das Eiergaren und stellen einen Timer ein. Sie starten das Toasten und stellen einen Timer ein. Während sie beide kochen, putzen Sie die Küche. Wenn die Timer ablaufen, nehmen Sie die Eier von der Hitze und dem Toast aus dem Toaster und servieren sie.
  • Asynchron, Multithreading: Sie stellen zwei weitere Köche ein, einen zum Kochen von Eiern und einen zum Kochen von Toast. Jetzt haben Sie das Problem, die Köche so zu koordinieren, dass sie beim Teilen von Ressourcen in der Küche nicht miteinander in Konflikt geraten. Und du musst sie bezahlen.

Ist es nun sinnvoll, dass Multithreading nur eine Art von Asynchronität ist? Beim Threading geht es um Arbeiter; Bei der Asynchronität geht es um Aufgaben . In Multithread-Workflows weisen Sie den Mitarbeitern Aufgaben zu. In asynchronen Single-Threaded-Workflows haben Sie ein Aufgabendiagramm, in dem einige Aufgaben von den Ergebnissen anderer abhängen. Wenn jede Aufgabe abgeschlossen ist, wird der Code aufgerufen, der die nächste auszuführende Aufgabe unter Berücksichtigung der Ergebnisse der gerade abgeschlossenen Aufgabe plant. Aber Sie brauchen (hoffentlich) nur einen Arbeiter, um alle Aufgaben auszuführen, nicht einen Arbeiter pro Aufgabe.

Es hilft zu erkennen, dass viele Aufgaben nicht prozessorgebunden sind. Für prozessorgebundene Aufgaben ist es sinnvoll, so viele Worker (Threads) einzustellen, wie Prozessoren vorhanden sind, jedem Worker eine Aufgabe zuzuweisen, jedem Worker einen Prozessor zuzuweisen und jeden Prozessor nichts anderes tun zu lassen, als das Ergebnis als zu berechnen so schnell wie möglich. Für Aufgaben, die nicht auf einen Bearbeiter warten, müssen Sie jedoch überhaupt keinen Bearbeiter zuweisen. Warten Sie einfach, bis die Nachricht eintrifft, dass das Ergebnis verfügbar ist, und machen Sie noch etwas, während Sie warten. Wenn diese Nachricht eintrifft, können Sie die Fortsetzung der abgeschlossenen Aufgabe als nächstes auf Ihrer To-Do-Liste zum Abhaken einplanen.

Schauen wir uns Jons Beispiel genauer an. Was geschieht?

  • Jemand ruft DisplayWebSiteLength auf. Wer? Es ist uns egal.
  • Es legt ein Label fest, erstellt einen Client und fordert den Client auf, etwas abzurufen. Der Client gibt ein Objekt zurück, das die Aufgabe darstellt, etwas abzurufen. Diese Aufgabe ist in Bearbeitung.
  • Ist es in einem anderen Thread in Bearbeitung? Wahrscheinlich nicht. Lesen Sie Stephens Artikel warum es keinen Thread gibt.
  • Jetzt warten wir auf die Aufgabe. Was geschieht? Wir prüfen, ob die Aufgabe zwischen dem Zeitpunkt ihrer Erstellung und dem Zeitpunkt, an dem wir darauf gewartet haben, abgeschlossen ist. Wenn ja, holen wir das Ergebnis und rennen weiter. Nehmen wir an, es ist noch nicht abgeschlossen. Wir melden den Rest dieser Methode als Fortsetzung dieser Aufgabe an und geben zurück.
  • Jetzt ist die Kontrolle an den Anrufer zurückgekehrt. Was tut es? Was auch immer es will.
  • Angenommen, die Aufgabe ist abgeschlossen. Wie hat es das gemacht? Möglicherweise wurde es auf einem anderen Thread ausgeführt, oder der gerade zurückgegebene Aufrufer hat es zugelassen, dass es auf dem aktuellen Thread vollständig ausgeführt wurde. Unabhängig davon haben wir jetzt eine abgeschlossene Aufgabe.
  • Die abgeschlossene Aufgabe fordert den richtigen Thread auf - wahrscheinlich auch den Thread only -, um die Fortsetzung der Aufgabe auszuführen.
  • Die Kontrolle geht sofort zurück in die Methode, die wir gerade am Punkt des Wartens verlassen haben. Jetzt gibt es is ein Ergebnis, damit wir text zuweisen und den Rest der Methode ausführen können.

Es ist genau wie in meiner Analogie. Jemand fragt Sie nach einem Dokument. Sie verschicken die Mail für das Dokument und erledigen weitere Arbeiten. Wenn es in der Mail ankommt, wird Ihnen signalisiert, und wenn Sie Lust dazu haben, erledigen Sie den Rest des Workflows - öffnen Sie den Umschlag, zahlen Sie die Zustellgebühren, was auch immer. Sie müssen keinen anderen Arbeiter einstellen, um das alles für Sie zu erledigen.

413
Eric Lippert

In-Browser-Javascript ist ein großartiges Beispiel für ein asynchrones Programm, das keine Threads hat.

Sie müssen sich keine Sorgen machen, dass mehrere Codeteile gleichzeitig dieselben Objekte berühren: Jede Funktion wird beendet, bevor ein anderes Javascript auf der Seite ausgeführt werden kann.

Wenn Sie jedoch so etwas wie eine AJAX) - Anfrage ausführen, wird überhaupt kein Code ausgeführt, sodass andere Javascript-Benutzer auf Dinge wie Klickereignisse reagieren können, bis diese Anfrage zurückkommt und den damit verbundenen Rückruf aufruft Eine dieser anderen Ereignisbehandlungsroutinen wird noch ausgeführt, wenn die AJAX) - Anforderung zurückkommt. Die Behandlungsroutine wird erst aufgerufen, wenn sie abgeschlossen ist. Es wird nur ein JavaScript-Thread ausgeführt, obwohl dies der Fall ist Sie können das, was Sie getan haben, so lange unterbrechen, bis Sie die Informationen haben, die Sie benötigen.

In C # -Anwendungen geschieht das Gleiche, wenn Sie mit UI-Elementen arbeiten - Sie dürfen nur mit UI-Elementen interagieren, wenn Sie sich im UI-Thread befinden. Wenn der Benutzer auf eine Schaltfläche geklickt hat und Sie darauf mit dem Lesen einer großen Datei von der Festplatte antworten möchten, könnte ein unerfahrener Programmierer den Fehler machen, die Datei im Click - Ereignishandler selbst zu lesen, wodurch die Anwendung bis zum "Einfrieren" der Datei "einfriert" Das Laden der Datei wurde beendet, da auf weitere Ereignisse im Zusammenhang mit dem Klicken, Schweben oder mit der Benutzeroberfläche nicht mehr reagiert werden darf, bis dieser Thread freigegeben wird.

Eine Option, mit der Programmierer dieses Problem möglicherweise vermeiden können, besteht darin, einen neuen Thread zum Laden der Datei zu erstellen und dann dem Code des Threads mitzuteilen, dass er beim Laden der Datei den verbleibenden Code auf dem UI-Thread erneut ausführen muss, damit die UI-Elemente aktualisiert werden können basierend auf dem, was es in der Datei gefunden hat. Bis vor kurzem war dieser Ansatz sehr beliebt, da die C # -Bibliotheken und die Sprache so einfach waren, aber er ist wesentlich komplizierter als er sein muss.

Wenn Sie darüber nachdenken, was die CPU tut, wenn sie eine Datei auf Hardware-/Betriebssystemebene liest, gibt sie im Grunde eine Anweisung aus, Datenstücke von der Festplatte in den Speicher zu lesen und das Betriebssystem mit einem "Interrupt" zu treffen, wenn Der Lesevorgang ist abgeschlossen. Mit anderen Worten, das Lesen von der Festplatte (oder wirklich von jedem E/A) ist von Natur aus eine asynchrone Operation. Das Konzept eines Threads, der auf den Abschluss der E/A wartet, ist eine Abstraktion, die die Bibliotheksentwickler erstellt haben, um das Programmieren zu vereinfachen. Es ist nicht nötig.

Jetzt verfügen die meisten E/A-Vorgänge in .NET über eine entsprechende ...Async() -Methode, die fast sofort einen Task zurückgibt. Sie können diesem Task Rückrufe hinzufügen, um den Code anzugeben, der ausgeführt werden soll, wenn der asynchrone Vorgang abgeschlossen ist. Sie können auch angeben, auf welchem ​​Thread dieser Code ausgeführt werden soll, und Sie können ein Token bereitstellen, das der asynchrone Vorgang von Zeit zu Zeit überprüfen kann, um festzustellen, ob Sie sich entschlossen haben, die asynchrone Aufgabe abzubrechen. Auf diese Weise können Sie die Arbeit schnell beenden und anmutig.

Bis die Schlüsselwörter async/await Hinzugefügt wurden, war es in C # viel offensichtlicher, wie der Rückrufcode aufgerufen wird, da diese Rückrufe in Form von Delegaten vorlagen, die Sie der Aufgabe zugeordnet haben. Um Ihnen weiterhin den Vorteil der Verwendung der ...Async() -Operation zu bieten und gleichzeitig die Komplexität des Codes zu vermeiden, abstrahiert async/await Die Erstellung dieser Delegaten. Aber sie sind immer noch im kompilierten Code enthalten.

So können Sie Ihrem UI-Ereignishandler await eine E/A-Operation zuweisen, den UI-Thread für andere Aufgaben freigeben und nach dem Lesen der Datei mehr oder weniger automatisch zum UI-Thread zurückkehren --ohne jemals einen neuen Thread erstellen zu müssen.

19