C# Async und await

DeepComputer

Banned
Registriert
Apr. 2020
Beiträge
266
Hallo,

ich brauch bitte etwas Hilfe. Folgendes Projekt https://github.com/OneMillionthUsername/KriegDerKerne/tree/Async
Ich schaffe es einfach nicht, dass sich die Gegner, der Laser und der Spieler sozusagen unabhängig von einander gleichzeitig zu bewegen. Und wie kann ich sozusagen asynchron Eingaben einlesen, zB wenn der Spieler D drückt -> fliege nach oben?

Irgendwie blockiere ich immer das Program und ich weiß nicht wie. Das ist das erste mal, dass ich async und await verwende
und ich kann es noch nicht verstehen.
Könnte mir vllt jemand zeigen wie ich das machen kann?

mfg
 
  • Gefällt mir
Reaktionen: Tanzmusikus
Ich hab grob mal deine KriegDerKerne.cs überflogen.
Kurzes Fazit: Falsches Konzept.
Async Await ist primär für i/o-lastige operationen gedacht, also zb für Dateioperationen, Socketoperationen usw. und ist dann nützlich wenn diese Operationen eine Notifikation/WaitHandle/IO-Completion Port notifizieren können wenn sie fertig sind, damit deren Weiterverarbeitung erfolgen kann.

Was du willst ist ein fixer Thread für das Einlesen deiner Tastatureingaben und ggf einen Thread für das Abarbeiten, der die daraus erzeugten Operationen abarbeitet.

Schmeiß weg, fang von vorne an, vergiß async/await.

Man kann das konzeptionell auch als Publisher/Subscriber System lösen (z.b. via Channels), aber das ist weit komplizierter. Du kannst das auch alles auf dem Main Thread lösen, klassisch, am Anfang der Schleife die Tastatur Operationen einlesen und dann drauf reagieren. Gibt auch noch diverse weitere Wege ;)
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Tanzmusikus und andy_0
Tornhoof schrieb:
Was du willst ist ein fixer Thread für das Einlesen deiner Tastatureingaben und einen Thread für das abarbeiten der die daraus erzeugten Operationen abarbeitet.
Hast du bitte einen Tipp wie das aussehen könnte?
Etwa so:
thread erstellen {lese input}
thread erstellen {get input -> do stuff}

?
Ergänzung ()

Tornhoof schrieb:
Man kann das konzeptionell auch als Publisher/Subscriber System lösen (z.b. via Channels), aber das ist weit komplizierter. Du kannst das auch alles auf dem Main Thread lösen, klassisch, am Anfang der Schleife die Tastatur Operationen einlesen und dann drauf reagieren. Gibt auch noch diverse weitere Wege ;)
Ja bitte hilf mir ein bischen. Ich muss mich da erst einlesen und arbeite eigentlich zum ersten mal mit asynchrone sachen. pls give me some input :)
 
Ich sehe das ähnlich wie Tornhoof. Ein extra Bearbeitungsthread würde mehr Sinn ergeben. Auf alle Fälle halte ich dein Beispiel für zu komplex für etwas, was man nicht versteht.

Ich empfehle dir ein viel einfaches Beispiel zu erzeugen (z.B. Eingabe ausgeben, während parallel eine Methode für x Sekunden pausiert oder ähnliches) um dein Verhalten nachzustellen.
Sobald du sowas hast, was Mal gehen soll, aber nicht geht, kannst du es behutsam erweitern, bis es funktioniert.

Thread Ansatz:
Du hast deine Einstiegsmethode. Diese erzeugt zwei Threads (Verarbeitung, Eingabe). Alles passiert ab sofort in den Threads, nicht mehr in der Einstiegsmethode.
Zum Datenaustausch zwischen den Threads nutzt du eine statische, thread sichere Liste (heißt ConcurrentQueue denke ich).
 
  • Gefällt mir
Reaktionen: Tanzmusikus und Tornhoof
Eh, async/await ist natürlich nicht nur dafür da, damit man vorhandenen Kram damit weiterverarbeiten kann :D

Ich gehe aber insoweit mit, daß ich zumindest nicht das Gefühl bekommen habe, daß klar ist, was async und await überhaupt machen. Das mag natürlich täuschen -- dann sorry.

Daher, wie schon angedeutet, ein paar kleine proof-of-concepts zusammenbauen. Einfach um zu sehen was da wie passiert. Welche Auswirkung "async" und "await" haben. Weitergehend, was man mit "yield" machen kann. Und ja, was man mit konkreten Threads machen kann.

Multithreading egal in welcher Ausprägung muß man sich erstmal mit auseinandersetzen, bevor man da anfängt. Wer nur lineare Programmierung gewöhnt ist - "Batch" also --- der fällt hier ganz schnell auf die Nase, denn der Determinismus ist raus und nur weil es in der zweiten Zeile steht, wird es nicht als zweites ausgeführt.

Deshalb erstmal klein anfangen.
 
async await ist eben kein Multi-Threading, sondern Concurrency. Dein Programm läuft weiterhin nur mit einem Thread, nur wird dieser eben nicht mit langen Operationen wie z.B. einer großen Datei einlesen oder etwas von der Datenbank abfragen, blockiert.

Wie die anderen aber schon sagten, ist für deinen Fall async await der falsche Ansatz und du brauchst mehrere Threads, um das sauber zu handhaben.

Problem an Multi-Threading ist die Komplexität, die damit mit einhergeht. Dabei rede ich jetzt nicht von kleinen Dingen, die man mal "schnell" in einen eigenen Thread auslagert, sondern von eben einem Fall, der deinem schon näher kommt: Threads können sich nämlich blockieren.

Bevor du dich also an dein eigentliches Problem sitzt, klein anfangen und versuchen, erste Erfahrungen mit Threads zu sammeln.

Als Startpunkt kannst du ja mal z.B. diesen kurzen Artikel durcharbeiten. Ich würde mich dann darauf aufbauend von einem Fall zum nächst komplexeren hangeln.
 
  • Gefällt mir
Reaktionen: Tanzmusikus
Ja danke, ihr habt recht.
Das Ding war halt, dass ich so angefangen hab mal zwei random punkte zu zeichnen die sich zufällig bewegten. Dann hab ich mir die Klassen und dieses und jenes erstellt, aus den Punkten eben Raumschiffe gemacht,
dann den Player hinzugefügt und den Laser. Erst danach hab ich mir gedacht ich implementiere jetzt noch das Schießen und Töten hinzu. Bis dahin hat alles so super funktioniert, da wollt ich gleich wieter machen. In Wirklichkeit hab ich aber null Ahnung von async und multithreading - und gestern paar Bier noch dazu ^^ also ja...
Ich glaub heute freut es mich nicht mehr aber morgen werde ich mir das Multithreading genauer ansehen.
 
Zuletzt bearbeitet:
Kein Ding. Eines der wichtigsten Dinge die man in der angewandten Informatik oder Ingenieurwissenschaften lernen muss ist, dass man das Problem soweit wie möglich vereinfacht, damit man es besser erkennen und anschließend lösen kann. Sobald man die Lösung / das Problem verstanden hat, kann man skalieren.
 
DrCox1911 schrieb:
async await ist eben kein Multi-Threading, sondern Concurrency. Dein Programm läuft weiterhin nur mit einem Thread, nur wird dieser eben nicht mit langen Operationen wie z.B. einer großen Datei einlesen oder etwas von der Datenbank abfragen, blockiert.
Ist nach meinem Verständnis und nach meiner Erfahrung nicht korrekt. Natürlich wird in einem anderen Threads weitergearbeitet sonst wär der aktuelle ja immer noch blockiert. Es wird nur defaultmäßig in den Ausgangsthread zurückgekehrt. Rufst Du ConfigureAwait(false) auf dem Task auf, erfolgt die weitere Verarbeitung im Arbeitsthread.
 
  • Gefällt mir
Reaktionen: Tanzmusikus
DrCox1911 schrieb:
Dein Programm läuft weiterhin nur mit einem Thread, nur wird dieser eben nicht mit langen Operationen wie z.B. einer großen Datei einlesen oder etwas von der Datenbank abfragen, blockiert.
async await funktioniert auch mit mehreren Threads

Tornhoof schrieb:
Was du willst ist ein fixer Thread für das Einlesen deiner Tastatureingaben und ggf einen Thread für das Abarbeiten, der die daraus erzeugten Operationen abarbeitet.
Klingt für mich sehr IO-lastig

Und eigentlich kann man das auch ohne Probleme mit (async) Tasks abbilden; keine Notwendigkeit für explizite Threads
 
Zuletzt bearbeitet:
Man kann das auch mit async-await handlen. Auch hier: baue dir eine einfache Problemstellung und fange dann an die Lösung zu implementieren.

Zum Thema async-await Threading: es läuft "im selben Thread" als Task. Das besondere ist, dass die Abarbeitung aufgeteilt wird. Mit Task.Run() hingegen erzeugst du ein Task auf einem Thread im Thread Pool. Das sind aber sehr spezifische Details, die für hier egal sind.

Siehe auch für mehr Informationen: https://blog.stephencleary.com/2012/02/async-and-await.html
 
Ja ich weiss, hätt mich aber trotzdem interessiert wie es aussehen würde, aber nur wenns wirklich schnell ginge. Was ich leider weniger glaube, weil der Code ja an sich auch nicht richtig funkioniert. Ich mein, es läuft darauf hinaus alles neu zu machen. Aber wenns möglich wäre in Kürze zB das Schiessen und das Bewegen asynchron zu machen, wäre es schön zu sehen. Ist mir gestern sogar irgendwie gelungen, aber alles andere hat dann nicht funktioniert ^^
 
Zuletzt bearbeitet:
Der Task ist das Promise und keine ausführbare Einheit an sich, das kann nur ein Thread sein.

Hier steht
https://docs.microsoft.com/de-de/dotnet/api/system.threading.tasks.task?view=net-6.0 :
Because the work performed by a Task object typically executes asynchronously on a thread pool thread rather than synchronously on the main application thread, you can use the Status property, as well as the IsCanceled, IsCompleted, and IsFaulted properties, to determine the state of a task.

Also in der Regel wird es in einem anderen Threads des Threadpools ausgeführt und nicht im Main Thread.

Kann morgen auch mal ein schnelles Codebeispiel bereitstellen wo man das sieht.
 
KitKat::new() schrieb:
Klingt für mich sehr IO-lastig

Und eigentlich kann man das auch ohne Probleme mit (async) Tasks abbilden; keine Notwendigkeit für explizite Threads
Tasks kommen beim default ThreadScheduler aus'm ThreadPool bei async/await, der ist explizit nicht für langlaufende Operationen gedacht, sondern um den Overhead der Threaderzeugung zu reduzieren. OPs Aufgabe (wenn man sie Multithreaded betrachtet) mindestens einen Thread für die Eingabeverarbeitung, der läuft die ganze Zeit. Ist damit also eigentlich nicht für den Pool gedacht.
Natürlich hast du Recht, dass es damit auch geht, nur wird's halt nicht empfohlen.

Und bzgl. io-lastig, daher habe ich die Abhandlung zu Waithandles etc dahinter aufgeführt. Seine Console Operationen sind alle synchron, Console hat keine async API. Das wrappen von sync in async, um des async willens, ist üblicherweise ein anti-pattern. Siehe u.a. https://devblogs.microsoft.com/pfxt...synchronous-wrappers-for-synchronous-methods/ (und für die andere Richtung https://devblogs.microsoft.com/pfxt...ynchronous-wrappers-for-asynchronous-methods/)

Eigene TPL (Task) Methoden zu schreiben, die selber keine weiteren async Methoden aufrufen, bietet sich üblicherweise nur an, wenn man Operationen des OS aufruft die Callback/Waithandle Funktionalität haben, also deinen Code notifizieren können wenn eine entsprechende Operation fertig ist (zb Socket Connect beim Verbindungsaufbau zur Datenbank/Http), dafür stellt das OS sehr effiziente Mechanismen zur Verfügung um diese completion Aufrufe zu machen, je nach OS und Implementierung ist die einfachste Variante ein polling Thread der alle Waithandles kontinuierlich prüft um festzustellen welcher aktuell schlafender thread notifiziert werden muss.

Das ist alles nicht der Fall in dem Beispiel.
99% der Fälle wo man async await nutzen muss, ist weil man eine API nutzen will die TPL (Task Parallel Library) APIs nutzt.

TPL selber richtig zu nutzen ist hinreichend schwer, insb. die Fehlerbehandlung, hat MS ja auch selber realisiert und async/await eingeführt und das Programmiermodell zu vereinfachen.

Bei den ganzen unterschiedlichen Möglichkeiten in .NET für die parallele Abarbeitung von Operationen ist es zugegebenermaßen einfach den Überblick zu verlieren und zugegebenermaßen ist async/await eine einfach zu nutzende Variante für sehr sehr viele Probleme.

Ansonsten wurde schon oben der Link zu Stephen Clearys Blog gepostet, der geht noch weit detaillierter auf die diversen Unterschiede ein.
 
  • Gefällt mir
Reaktionen: DrCox1911 und Tanzmusikus
Code:
                Thread checkShoot = new(() => CheckInputAndShoot(entities, enemies, player));
                Thread playerMove = new(() => player.Move());
                checkShoot.Start();
                playerMove.Start();
also ich hab das jetzt so gemacht und es funktioniert asynchron. Aber die Graphik ist fehlerhaft, weil sich das Zeichnen der Objekte selbst überschneidet. Hab den commit upgedated
https://github.com/OneMillionthUsername/KriegDerKerne/tree/Async/KriegDerKerne
 
Dein Ansatz für einen Test ist leider weiterhin falsch, du hast die Komplexität nicht reduziert.

Warum es nicht funktioniert: du rufst die Threads in einer Schleife auf. Wie ich schon gesagt habe, rufst du im Main NUR die Threads auf. Anschließend darauf achten: einer der Threads sammelt Daten, der andere agiert auf die Daten (z.B. zeichnet auf die Oberfläche). Und zwar laufen sie in jeweils einer Endlosschleife mit z.B. 20 ms Pausen (d.h. etwa 50 Mal pro Sekunde).
 
andy_0 schrieb:
Dein Ansatz für einen Test ist leider weiterhin falsch, du hast die Komplexität nicht reduziert.
Ja, ich weiß eh. erwarte nicht zuviel von mir, noch bin ich am experimentieren.
andy_0 schrieb:
Wie ich schon gesagt habe, rufst du im Main NUR die Threads auf. Anschließend darauf achten:
Ja, das hatte ich auch so gemacht, aber hat so leider nicht funktioniert.
Ja wie gesagt, ist noch experimentell, um es einbischen besser zu verstehen. Ich verstehe ja zB immer noch nicht was ein "Thread" eigentlich ist und ich hab niemanden ders mir erklärt ^^
Ich verstehe jetzt darunter sowas wie einen Platz wo ein bestimmter Code ausgelagert wird.
 
Tornhoof schrieb:
Tasks kommen beim default ThreadScheduler aus'm ThreadPool bei async/await, der ist explizit nicht für langlaufende Operationen gedacht, sondern um den Overhead der Threaderzeugung zu reduzieren.
Async Tasks sind gelaufigerweise nicht besonders langläufig, da sie ja nur laufen während eine Eingabe verarbeitet wird und beim Rest der Zeit "schlafen".
Kann ich daher nicht nachvollziehen

Tornhoof schrieb:
Seine Console Operationen sind alle synchron, Console hat keine async API.
Stimmt...

Tornhoof schrieb:
Das wrappen von sync in async, um des async willens, ist üblicherweise ein anti-pattern. Siehe u.a. https://devblogs.microsoft.com/pfxt...synchronous-wrappers-for-synchronous-methods/
Bei der Empfehlung geht's aber um libraries. Der TE schreibt keine library. Es wird sogar gezeigt wann es (nicht) sinnvoll ist das zu machen.

Aber ja, mit dem fehlen von async Konsoleneingabe, hat sich die Sinnhaftigkeit von async schon reduziert.
 
Zurück
Oben