C# Async und await

Ein Thread ist ein Stück Programmcode (egal wie complex), der auf der CPU vom Betriebssystem extra ausgeführt wird (einfach gesagt). Initial ist deine Anwendung ein einzelner Thread. In diesem läuft alles nacheinander ab. Der Ablauf ist deterministisch.
Sobald du mehr als einen Thread hast, laufen diese voneinander unabhängig. Du weißt nicht mehr, welcher Befehl zuerst aufgeführt wird, wenn du verschiedene Threads miteinander vergleichst. Jeder Thread für sich ist deterministisch, als eine gesamthafte Betrachtung weißt du aber nicht was gerade auf der CPU an der Reihe ist.

Weil du das nicht weißt, dürfen Threads sich nicht in ihren Aufgaben überschneiden z.B. beide auf die Oberfläche zeichnen. Wenn sie das tun müsstest du sie "synchronisieren" d.h. aufpassen daß ihre Daten und Zustände "gleich" sind. Da das sehr schwierig ist, sollte man darauf nach Möglichkeit verzichten.

Jetzt kommen wir zu deinem Beispiel:
Du erzeugst zwei Threads. Du weißt nicht wer wie viel Rechenleistung erhält und damit herrscht ein gewisses Chaos in der Ausführung. Deswegen sollten diese voneinander unabhängig sein. T1 isses egal ob der User Daten tippt oder nicht, T2 isses egal ob es Daten zum zeichnen gibt ABER wenn es was gibt handeln sie entsprechend.
Die einzige Verbindung zwischen T1 und T2 ist die statische ConcurrentQueue, die du erzeugen musst, worüber T1 dem T2 die Daten zum zeichnen liefert.
 
  • Gefällt mir
Reaktionen: RalphS
Ja, dann hab ich's eh halbwegs verstanden. Jetzt muss ich mir noch die Syntax einprägen wie ich einen Thread erstelle und ihn sozusagen abkapsle damit er sich nicht mit anderen überschneidet. Und auch ob er parametrisiert ist oder nicht. Weil auch da bin ich mir unsicher. So wie es in meinem Buch steht
Thread t = new Thread(new ParametrizedThreadStart(DoSomething));
t.Start(obj);
So hats nicht funktioniert. Deshalb hab ich Lambda verwendet und ich weiß nicht ob zu recht oder nicht.
Ich verstehe auch noch nicht was der Unterschied von ThreadStart und Thread eigentlich ist.
Ist ThreadStart(x) das gleiche wie Thread x = new() -> x.Start()?
 
So wie du das im Code gemacht hast (als Lambda), so isses schon gut. Ich sehe keinen Grund Parameter zu übertragen.

Main erzeugt zwei Threads (T1 und T2).
T1 liest in einer Endlosschleife von Console und schreibt die Eingaben, wenn vorhanden, in die ConcurrentQueue.
T2 liest in einer Endlosschleife aus der ConcurrentQueue und zeichnet die Daten, wenn vorhanden.

Auch hier: baue dir erst Mal ein einfaches Beispiel d.h. nur T1 der die Daten ausliest und für den Test zurück auf die Console schreibt.

Zur Frage new Thread().Start() oder ThreadStart(). Hab hier gerade kein VS aber bei dem einen erzeugst und startest du einen Thread, während das andere einen bereits erzeugten Thread startet. Die erste Variante ist also zwei Anweisungen in einem.
 
Ja, das macht Sinn. Man, das sind immer so throwbacks wenn man was machen will aber vorher noch die Konzepte dazu von Anfang an lernen muss ^^ 🤮 Und wenn man damit mal durch ist, macht der Code eh schon keinen Sinn mehr ^^🤮🤮🤮 aber gleichzeitig kann man auch nicht alle Konzepte lernen und dann den perfekten Code schreiben. Das ist ein Jammer 🤮🤮🤮🤮🤮🤮A tale from a programmers life :)
 
Zuletzt bearbeitet:
Hey,

https://github.com/OneMillionthUsername/KriegDerKerne/tree/Async
ich habs jetzt mit der ConcurrentQueue versucht aber irgendwie versteh ich das immer noch nicht.
Macht das so sinn wie ich es gemacht hab?
Und wie kann ich die Tastaturabfragen schneller machen? Wenn ich
DO-WHILE
WENN Taste A gedrückt DANN mach Aktion
WENN Taste B gedrückt DANN mach Aktion
...
jede Taste mit einem If bei jeden Schleifendurchgang abprüfe dann wird das Program so langsam.
Und gibt es eine Möglichkeit unabhängig vom Cursor zu zeichnen? Weil die Threads würden soweit ich das verstehe jetzt asycnhron laufen, aber es kommt zu Grafikfehler, weil sie asynchron aber gleichzeitig an der selben Cursorposition zeichnen wollen. Oder liegt das daran, dass sie alle die selben Methoden nutzen?
 
Sollte das nicht eher so sein

Thread A => lese Taste(n) ein schreibe in Event Datenstruktur X
Thread B1 => lese Datenstruktur X schreibe Aktion X z.B. Bewegung in "Welt" Datenstruktur
...
Thread C => mache ALLE Grafikausgaben aus der "Welt" Datenstruktur

Objekte die einmal vorhanden sind und nicht jeweils pro Thread existieren wie Grafikausgabe die es halt nur 1 x gibt sollten und dürfen nicht von den einzelnen Threads gemacht werden.

Also nur Thread A liest Tastatus nur Thread C macht Grafikausgabe und beide sind nicht direkt Teil der "Spielfigurthreads" B1...B2 sondern übergeben Daten nur mittelbar.

Du solltest eine Datenstruktur schaffen die erst mal dien Bildschirmausgabe abtstrakt dastellt also z.B,. steht in einer Struktur Figur A steht auf Koordinate X,Y,Z ein anderen Thread berechnet Figur B steht auf X1,Y1,Z1 => beide schreiben ihre Werte in die abstrakte Welt.

Der Grafikausgabethread zeichnet dann anhand der Daten (sortiert die evtl nach Z usw usw) - direkt auf dem Bildschirm macht sonst niemand was.

Da musst Du halt dann Dinge noch reinmachen, dass Daten vollständig sind - (also sowas wie "Double Bufferings" etc) oder falls Rundenbasiert evtl versch Buffer die einen jeweiligen Zeitpunkt darstellen usw.

Mit "Buffering" musst Du nur als Beispiel verhindern, dass während die Grafikausgabe von Thread C gemacht wird die Figurthreads B1 und B2 teilweise ihre Koordinaten in der abstrakten Welt bereits wieder ändern. Also auch da trennen, das halt bei Threads oft wichtig.
 
Zuletzt bearbeitet von einem Moderator:
fgordon schrieb:
Thread A => lese Taste(n) ein schreibe in Event Datenstruktur X
Thread B1 => lese Datenstruktur X schreibe Aktion X z.B. Bewegung in "Welt" Datenstruktur
...
Thread C => mache ALLE Grafikausgaben aus der "Welt" Datenstruktur
Das klingt gut, werde ich probieren. Ich kenne zwar solche "Datenstrukturen" noch nicht aber das macht für mich schon Sinn. Werd das mal abarbeiten
 
Datenstruktur ist stellvertretend gemeint für alles was Daten aufnehmen kann und wo man die auslesen kann - wie man das dann konkret umsetzt ist je nach konkreter Umgebung etc anders. das kann dann alles sein von einem "Objekt" in C# bis zu einer Datenbank auf 50.000 Servern oder einem HW Schalter xD

"Leider" kommt man bei Threads und Co um viel Synchronisations- und Schnittstellenzeugs meist nicht herum, aber das macht es dann auch interessant.
 
Ich bin schon bissl Gaga im Kopf heute aber würde das einer Struktur nahekommen?
C#:
Thread checkInput = new(new ThreadStart(() => Input()));
Thread BufferThread = new(new ThreadStart(() => Buffer(Input(), player)));
Thread Draw = new(new ThreadStart(() => DrawGraphics(player)));

//starte die Threads
Threads(checkInput, BufferThread, Draw);

public static void Threads(params Thread[] threads)
{
    ConcurrentQueue<Thread> q = new();

    foreach (Thread thread in threads)
    {
        thread.Start();
        q.Enqueue(thread);
    }
}
public static ConsoleKey Input()
{
    //if (Console.ReadKey(true).Key == ConsoleKey.Spacebar)
    //{
    //    return ConsoleKey.Spacebar;
    //}
    if (Console.ReadKey(true).Key == ConsoleKey.A)
    {
        return ConsoleKey.A;
    }
    if (Console.ReadKey(true).Key == ConsoleKey.D)
    {
        return ConsoleKey.D;
    }
    if (Console.ReadKey(true).Key == ConsoleKey.W)
    {
        return ConsoleKey.W;
    }
    if (Console.ReadKey(true).Key == ConsoleKey.S)
    {
        return ConsoleKey.S;
    }
    return ConsoleKey.Spacebar;
}
public static void Buffer(ConsoleKey consoleKey, Entity entity)
{
    do
    {
        if (0 == consoleKey.CompareTo(ConsoleKey.Spacebar))
        {
            //Schussbereit
        }
        if (0 == consoleKey.CompareTo(ConsoleKey.A))
        {
            //move left
            entity.PosX -= 1;
        }
        if (0 == consoleKey.CompareTo(ConsoleKey.S))
        {
            //move down
            entity.PosY -= 1;
        }
        if (0 == consoleKey.CompareTo(ConsoleKey.D))
        {
            //move right
            entity.PosX += 1;
        }
        if (0 == consoleKey.CompareTo(ConsoleKey.W))
        {
            //move up
            entity.PosY += 1;
        }
    } while (true);
}
public static void DrawGraphics(Entity entity)
{
    entity.DeleteEntity();
    entity.DrawEntity();
}
 
fgordon schrieb:
Datenstruktur ist stellvertretend gemeint für alles was Daten aufnehmen kann und wo man die auslesen kann - wie man das dann konkret umsetzt ist je nach konkreter Umgebung etc anders. das kann dann alles sein von einem "Objekt" in C# bis zu einer Datenbank auf 50.000 Servern oder einem HW Schalter xD
Oder
Tornhoof schrieb:
https://devblogs.microsoft.com/dotnet/an-introduction-to-system-threading-channels/
 
Du kommst der grundsätzlichen Umsetzung näher DeepComputer. Dein Denkfehler ist aktuell noch, dass du meinst, die Threads gehören in eine Schleife. Das ist nicht korrekt, die Threads HABEN Schleifen.
Positiv ist, dass dein Beispiel langsam in Dimensionen vorrückt, wo man Inhalte / Konzepte einfach / vernünftig testen kann

Beispiele Pseudo Code:
Main:
{
var Queue = new ConcurrentQueue();
var ReadInputThread = new Thread();
var DrawThread = new Thread();

// Threads starten

// Endlos kaufen, bis App gewchlossen werden soll
while(true)
{
// Hier irgendwie prüfen wann alles geschlossen werden soll
If (shutdown)
{
// Alle Threads stoppen
return;
}

Thread.Sleep(10);
}
}

// Prüft immer ob Daten verfügbar sind, läuft maximal einmal alle 10 ms
ReadInputThread:
{
while(true)
{
var input = Console.Read()
{
Queue.TryAdd(input); // Eingabe zum zeichnen bereitstellen
}
Thread.Sleep(10);
}
}

// Prüft alle 10 ms ob was gezeichnet werden muss
DrawThread:
{
while(true)
{
If (Queue.HasData)
{
var input = Queue.TryGet()

Console.Write(input); // Irgendwie was zeichnen
}
Thread.Sleep(10);
}
}
 
Hey :cool_alt:

also ich hab den Code jetzt etwas überarbeitet und er funktioniert mal so zu 70% sag ich einmal.
Ich glaube auch das mit den Threads jetzt besser verstanden zu haben. Mir ist klar geworden, dass ich sie nur brauche, wenn etwas wirklich unabhängig stattfinden soll.
Einen Wunsch hätte ich aber noch - ich glaube ich muss die Draw Methode thread safe machen,
weil es bei der Bewegung oder beim Laserschießen immer zu Grafikbugs kommt. Das bekomm ich nicht hin.
Hat jemand eine Idee?
https://github.com/OneMillionthUsername/KriegDerKerne_MT
 
Ich kann dir Montag Vormittag oder Abend rückmelden. Vorher hab ich keine Zeit.
 
Hey hey,

ich hab jetzt den Code synchron geschrieben und ohne threads.
Läuft soweit fast ganz gut.
Wie kann ich prüfen ob eine Taste gedrückt wurde und erst dann den player bewegen? Ich habs versucht mit
using System.Windows.Input aber ich kann das nicht nutzen?!
Also es geht darum, dass er nur in die Methode reingeht wenn ich eine Taste gedrückt habe.
C#:
private static void VerändereWerte(List<Enemy> enemies, Player player)
{
    foreach (var item in enemies)
    {
        item.MoveRandom();
    }
    //IF TASTE GEDRÜCKT
    
            if (Console.KeyAvailable)
            {
                player.Move(); 
            }
}
pls help wie ich das lösen kann
 
doku KeyAvailable mit Bsp

Die Verwendung von System.Windows.Input o.ä. erhöht tatsächlich die Möglichkeiten, du müßtest dafür allerdings auf Fenster wechseln, also weg von der Console.
 
Hihi ich hab auch mal kurz reingeschaut auf dem Tablet also ohne VS aber da fiel mir gleich was auf

Code:
Die playermove Routine sollte sicher eher so sein

also z.b.

....

     if (!Console.KeyAvailable) return;

            ConsoleKey abc = Console.ReadKey(true).Key;

          


                if (abc == ConsoleKey.Spacebar)
                {
                    Laser laser = new(X, Y);
                }
                else if (abc == ConsoleKey.A)
                {
                    Console.SetCursorPosition(X, Y);
                    //DeleteEntity();
                    X -= 1;
                }
                else if (abc == ConsoleKey.D)
                {
                    Console.SetCursorPosition(X, Y);
                    //DeleteEntity();
                    X += 1;
                }
....

                ....


Du führst ja dauernd neue ReadKeys() aus in jeder If abfrage die folgt unabhönig davon ob da dann noch ein keyavailable ist.
 
Zurück
Oben