Autor Beitrag
C#
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Mi 10.09.14 22:58 
Hey Community

und schon das nächste Problem bei meinem TD Spiel... Meine Probleme werden komischerweise immer verrückter :nut:

Und zwar arbeite ich ja mit 3 verschiedenen Threads: Zeichen-, Update- und der normale GUI-Thread von WinForms.

Ich habe nichts an den Update oder Draw Routinen geändert. Ich habe nur eine neue Klasse erstellt, einen InputManager, der die Eingaben mit der Maus und der Tastatur regeln soll. Das geschieht über die Events entsprechender Controls, die ich zur "Überwachung" hinzufüge. Also nix mit DirectInput oder GlobalHook.

Mein Problem ist folgendes: ich starte ganz normal das Spiel und der leere Bildschirm wird gerendert wie er sein soll. Sobald sich aber an der Szene etwas ändert (Fenster wird vergrößert oder GameObjects werden hinzugefügt), hängen sich alle 3 Threads auf :/.
Es kommt keine Windowsmeldung von wegen "Anwendung reagiert nicht". Die GUI friert einfach ein, die Update- und Drawthreads werden nicht mehr getriggert.
Wenn ich dann im Debugger Pause mache, wirft er mich an die Stelle, an der die Graphics-Objekte für den Resize initialisiert werden.
Da manche Objekte von mehreren Threads gleichzeitig genutzt werden, habe ich an einigen Stellen im Code ein lock(lockObject) eingebaut. Eben auch in der InitGraphics()-Methode.

Wenn ich dann mit dem Debugger in der lock-Zeile stehe und dann einen Einzelschritt mit dem Debugger machen will (kein Prozedurschritt), erreiche ich nichteinmal die nächste Zeile, was einfach ein { wäre.

Wenn ich das richtig deute, heißt das, dass das lockObject noch anderweitig in Verwendung ist und noch nicht freigegeben wird. Ich habe keine Ahnung, da ich nichts an der Klasse geändert habe in der das lockObject angelegt ist.
Im Callstack steht auch nix ungewöhnliches.

Was ich schildere ist ein Deadlock oder? :?

Was mir bei der genaueren Analyse auffällt:
Ich habe 2 verschiedene Objekte, mit denen ich sperre: lockObject1 und lockObject2. Diese haben nichts miteinander zu tun, greifen aber beide auf das gleiche Objekt zu, nämlich das Control auf das gezeichnet wird. Der eine Thread ruft CreateGraphics() auf, während der Andere DrawingSurface.Invoke(invokeHelper, Cursor.Position) aufruft (hier ist auch eine Stoppmarke des Debuggers wenn ich ihn pausieren lasse).

Gehe ich richtig in der Annahme, dass neben dem lockObject auch Objekte innderhalb des lock-Blocks gesperrt werden? Das würde dann mein Problem erklären - denke ich :gruebel:

Update
Ich glaube ich habe das Problem gefunden. Der Update-Thread kommt in einen lock-Block. In Diesem wird auf den GUI-Thread (mittels Invoke) zugegriffen, welcher wiederum auf einen Codeabschnitt in der anderen Klasse zugreift, der mit dem gleichen Objekt gelockt werden soll. Das wäre dann ein Deadlock :mrgreen:

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4799
Erhaltene Danke: 1059

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Do 11.09.14 11:13 
Hallo C#,

warum verwendest du überhaupt verschiedene Threads? In WinForm-Projekten solltest du möglichst alle GUI bezogenen Aufgaben im Hauptthread erledigen lassen (ein Control.Invoke-Aufruf bedeutet ja einen Threadwechsel d.h. Kontextwechsel, und dieser kostet Performance).
Selbst in Game-Frameworks (wie XNA o.ä.) wird auch immer nur eine Game-Loop innerhalb des Hauptthreads verwendet, welche UpdateRender und UpdateLogic aufruft.

Solange du nicht DirectX oder OpenGL verwendest, sollte die Aktualisierung der Anzeige auch immer nur Timer- bzw. ereignisbasiert erfolgen. Ein Echtzeitrendering kriegst du mit WinForms eh nicht (richtig) hin.

Für diesen Beitrag haben gedankt: C#
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Do 11.09.14 12:44 
Hmm. Ich dachte Logik und Grafik laufen in unterschiedlichen Threads, wegen fps einbrüchen und so :gruebel:. Und (ich muss leider gestehen :mrgreen:) ich zeichne momentan noch nicht synchronisiert mit dem GUI-Thread der Form, sondern im Grafik-Thread.
Sehe ich das dann richtig, dass einfach ein Thread mit der GameLoop läuft und dann zuerst die Logik und danach die Grafik aktuallisiert wird? Das würde meine Sache schon enorm erleichtern :D. Ich probiere mal alles in einen Thread zu packen. Mal sehen ob das klappt :D

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4799
Erhaltene Danke: 1059

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Do 11.09.14 12:56 
Die Logik sollte natürlich nur in bestimmten Zeitsteps aktualisiert werden (z.B. 25Hz), da ansonsten Bewegungen ja abhängig von der CPU-Performance wären.
Und solange du keine Partikel-Effekte (mittels Pixelshader) o.ä. hast, braucht auch die GUI dann nur bei einem Logikupdate aktualisert werden.

Für diesen Beitrag haben gedankt: C#
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Do 11.09.14 13:07 
Ja ok. Bisher habe ich beide Threads mit 62Hz getriggert (-> 16ms, weil der Timer nur integer akzeptiert). Dann verlagere ich das ganze mal auf einen Thread.

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Do 11.09.14 13:17 
Zitat:
Hmm. Ich dachte Logik und Grafik laufen in unterschiedlichen Threads, wegen fps einbrüchen und so


Ja aber aus anderen Threads ständig in den Hauptthread reinzusynchronisieren ist auch nicht wesentlich besser.
Und wenn du im gleichen Codepfad irgendwas lockst und in den Hauptthread rein invokest wird das zwangsweise Probleme geben.
Nicht zwangsweise Deadlocks aber zumindest schwer kontrollierbares Timing.

Ein Invoke sollte auch unnötig sein wenn man das ~wirklich~ trennt.
Der/Die Thread(s) für die Logik aktualisieren ständig nur den Datenbestand der den Spielzustand darstellt (völlig UI Unabhängig) und
der Hauptthread pollt über den Datenbestand ab und ermittelt daraus was, wo, wie zu zeichnen ist. Je nachdem wie man die Struktur des Datenbestands aufbaut
muß man eventuell nicht mal den zu irgendeinem Zeitpunkt locken (z.B in dem der quasi immutable ist und von der Logik immer komplett in einer atomaren Aktion getauscht wird).
Dafür kann man dann im Hauptthread auch eine eigene Schleife um die WndProc bzw. Application.ProcessMessages machen und selber das Timing kontrollieren ohne Timer.

Für diesen Beitrag haben gedankt: C#
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Fr 12.09.14 11:18 
Ich habe noch nie in groß in die Nachrichtenschleife eingegriffen. Könntest du ein kleines Codeschnipsel schreiben wie ich das Timing handeln kann?

// EDIT

Was mir gerade noch einfällt: momentan zeichne ich auf ein Control (nicht direkt auf die Form). Gibt das Probleme? Die Messageloop gehört ja immer nur zu einem Fenster oder?

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Fr 12.09.14 15:24 
Zitat:
Ich habe noch nie in groß in die Nachrichtenschleife eingegriffen. Könntest du ein kleines Codeschnipsel schreiben wie ich das Timing handeln kann?


Im simpelsten Fall muß man da auch nicht so nah dran. Es reicht eine eigene Schleife die sich selbst regelmäßig invalidiert und die MessageSchleife anstößt damit alles andere auch weiterhin läuft.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
int frequency = 50;
DateTime lastPaint = DateTime.Now;

while (MEINELIEBEABBRUCHBEDINGUNG)
{
    if (lastPaint.AddSeconds(1 / frequency) <= DateTime.Now)
    {
       DASCONTROLAUFDEMICHZEICHNE.Invalidate();  // lößt Paint aus
       lastPaint = DateTime.Now;
    }                
    Application.DoEvents();                
}


Edit: Beispiel hinzugefügt

RandomGameObjectMover - der Hintergrundthread der irgendwas anstellt
GameState - das Ding das vom RandomGameObjectMover verändert wird und dann regelmäßig auf der Form gezeichnet wird
Einloggen, um Attachments anzusehen!

Für diesen Beitrag haben gedankt: C#
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Fr 12.09.14 15:56 
Ach jetzt hab ich ess geblickt :mrgreen:. Ich habe mich zuerst gefragt wo die while-Schleife hin soll, ohne dass die GUI einfriert, aber das wird durch Applications.DoEvents() verhindert, oder? Dort werden alle ansethenden Nachrichten abgearbeitet.

Was den Logikthread betrifft: ich sollte das praktisch wie beim DoubleBuffering machen? Also eine Klasse erstellen, die alle veränderbaren Objekte beinhaltet und von dieser Klasse dann 2 Instanzen erzeugen und diese dann bei jedem Update flippen? So, dass immer aus einer gelesen und in die Zweite geschrieben wird.

Ich mach Meldung ob es klappt :)

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4799
Erhaltene Danke: 1059

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Fr 12.09.14 16:48 
Sorry, ihr beiden, aber lest euch mal Warum DoEvents Mist ist! durch.

Außerdem würde die obige Schleife (ohne ein Thread.Sleep(0// oder 1) die Prozessorlast (zumindestens für einen Kern) auf 100% treiben.
Ich bleibe bei meiner Aussage:
Th69 hat folgendes geschrieben:
... sollte die Aktualisierung der Anzeige auch immer nur Timer- bzw. ereignisbasiert erfolgen.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Fr 12.09.14 17:02 
Zitat:
Sorry, ihr beiden, aber lest euch mal Warum DoEvents Mist ist! durch.


Nur eine Frage des Anspruchs wie gut man etwas lösen will. Ab einen gewissen Punkt ist DoEvents sicher eine gefühlte Niederlage.
Ich bin mir aber nicht zu Schade es einzusetzen wenn ich das System ansonsten unter Kontrolle habe.
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Sa 13.09.14 17:14 
Hmm dann werde ich wohl Abstand von DoEvents() nehmen. Aber was Timer betrifft: der einzige Timer der synchron zu GUI arbeitet ist der WinForms Timer, oder? Der bekommt aber keine 60Hz -> 16ms hin...

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4799
Erhaltene Danke: 1059

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Sa 13.09.14 18:57 
Exakt nicht 60Hz, sondern ein bißchen drüber (die Auflösung liegt so bei ca. 14-15ms, d.h. ca. 66-71Hz).

Für diesen Beitrag haben gedankt: C#
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Sa 13.09.14 23:23 
Ok. Dachte der kann gar nicht so schnell, aber ein Test hat ergeben, dass er 15,6ms maximal schafft, was 64Hz entspricht.

Dann probier ich es mal so.

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Sa 13.09.14 23:56 
Okay jetzt habe ich schwere Performanceeinbrüche. Ich habe nur die ganzen locks entfernt und die beiden Timer durch WinForms Timer ersetzt. Jetzt hab ich schon bei einer leeren Szene (keine GameObjects) fps Zahlen von 33 bis 70Hz!! Was läuft jetzt da wieder schief :gruebel:. Als Intervall habe ich 16ms angegeben.

Der Zeichentimer ist jetzt mit dem Control synchronisiert (via Control.Invalidate()), aber BEIDE Timer haben starke Schwankungen :?

/// NACHTRAG

Noch eine Frage nebenbei: Mit this kann ich ja unter anderem aus einer Methode raus auf Klassenvariablen zugreifen, die den gleichen Namen wie eine lokale Variable tragen, da die Klassenvariable ja sonst verdeckt würde. Bei einer statischen Methode in einer statischen Klasse ist this ja nicht zulässig. Gibt es da einen anderen Weg lokale und Klassenvariablen anzusprechen.

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4799
Erhaltene Danke: 1059

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: So 14.09.14 09:32 
Hallo C#,

bei Angabe von 16ms nimmt er entweder 15 oder 30 (wegen meiner angesprochenen Auflösung von ca. 14-15ms).
Setze das Intervall einfach auf unter 15 (ich habe bei meinem FastDriver den Timer auf 14 gesetzt). Solange sich die Auflösung nicht ändert, kannst du sicherheitshalber den Wert auch auf 10 oder sogar auf 1 setzen.

Edit: falls du doch einen auf Millisekunden genauen Timer benötigst, kannst du per P/Invoke den Multimedia-Timer von Windows verwenden (s. Anhang)

PS: einfach Klassenname.Eigenschaft schreiben (aber bitte demnächst immer nur eine Frage pro Thema und wenn es noch Rückfragen dazu gibt, dann erstelle ein neues Thema im passenden Subforum).
Einloggen, um Attachments anzusehen!

Für diesen Beitrag haben gedankt: C#
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: So 14.09.14 12:03 
Also der WinForms Timer ist bei mir absolut nicht zuverlässig, selbst wenn ich als Intervall 1ms nehme, habe ich Frequenzen von 38Hz bis 180Hz. Den Multimediatimer hatte ich vorher genommen, aber der Tickt bei mir in einem anderen Thread. Hatte vergessen ihn zu syncen :autsch:. Jetzt läuft der wieder stabil mit seinen 16ms

Ab jetzt nur noch eine Frage pro thread ;). Klassenname.Eigenschaft natürlich :autsch:

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler