2017-02-11 6 views
4

Wir sind ein Multi-Thread-Game-Engine in C# entwickeln, und wir haben das Problem, dass wir das STAThread Attribut (oder setzen unsere Threads STA manuell) aktivieren müssen per Drag & -Drop-Unterstützung (AllowDrop kann nicht ohne STA gesetzt werden). Wenn wir jedoch STA aktivieren und die Aktualisierungsmethode länger dauert als die Draw-Methode (wie unten gezeigt), verhält sich das Fenster nicht mehr richtig - wenn es in der Taskleiste angeklickt wird, minimiert und maximiert es nicht wie erwartet es. Das genaue Verhalten ist auf verschiedenen Systemen unterschiedlich, ich vermute, dass hier einige Rennbedingungen zum Tragen kommen.Formular weirdly verhalten, wenn STA mit und Threads zu lange dauern

Hier ist unser Testcode:

[STAThread] 
    public static void Main() 
    { 
     Form form = new Form(); 
     form.Show(); 

     Barrier barrier = new Barrier(2); 

     Thread updateThread = new Thread(() => { 
      while (true) 
      { 
       barrier.SignalAndWait(); 
       Thread.Sleep(30); //Update 
       barrier.SignalAndWait(); 
      } 
     }); 
     updateThread.Start(); 

     while (true) 
     { 
      barrier.SignalAndWait(); 
      Thread.Sleep(15); //Draw 
      barrier.SignalAndWait(); 
      Application.DoEvents(); 
     } 
    } 

Antwort

5

ich dieses Problem erkennen, ich habe ein sehr ähnliches Problem wieder in den Vista Tage in einer nicht verwalteten App korrigiert. Das Problem ist ziemlich unklar und hängt mit der sehr temperamentvollen WM_ACTIVATEAPP-Nachricht zusammen, die Sie durch Klicken auf die Taskleistenschaltfläche generieren. Insbesondere, wenn es von einer verschachtelten Nachrichtenschleife ausgelöst wird und diese Schleife die Nachrichtenfilterung durchführt, um nur bestimmte "sichere" Nachrichten zu versenden.

Es wird durch den Aufruf Barrier.SignalAndWait() verursacht. Das blockiert den UI-Thread und das ist für einen STA-Thread nicht zulässig. Die CLR tut etwas dagegen, sie pumpt ihre eigene Nachrichtenschleife, während das zugrundeliegende Synchronisationsobjekt nicht signalisiert wird. Wenn es ist, die Schleife, die WM_ACTIVATEAPP sendet, und die Chancen sind ziemlich hoch, weil Sie für 30 ms blockieren und nur einmal pumpen, dann geht es schief. Aus irgendeinem mysteriösen Grund werden nachfolgende Nachrichten nicht gesendet. Fast sicher verursacht durch die Filterung durch diese Nachrichtenschleife. Sonst schwer zu sehen, wurde dieser Code nie veröffentlicht und unmöglich zu dekompilieren.

Es ist reparierbar, aber nicht leicht. Die Problemumgehung besteht darin, die CLR auf nicht zu zwingen, diese Nachrichtenschleife zu pumpen. Was in Ordnung ist, weil Ihre Wartezeiten kurz sind. Etwas, das Sie tun können, indem Sie die SynchronizationContext.Wait() -Methode überschreiben. Leider ist das schwer zu bewerkstelligen, die WindowsFormsSynchronizationContext-Klasse ist versiegelt und kann daher nicht abgeleitet werden, um ihre Wait() -Methode zu überschreiben. Sie müssen die Reference Source besuchen und die Klasse in Ihren Code kopieren/einfügen. Gib ihm einen anderen Namen.

Ich werde die kurze Version davon geben, zu zeigen, was Sie ändern müssen:

using System.Runtime.InteropServices; 

class MySynchronizationContext : SynchronizationContext { 
    public MySynchronizationContext() { 
     base.SetWaitNotificationRequired(); 
    } 
    public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) { 
     return WaitForMultipleObjects(waitHandles.Length, waitHandles, false, millisecondsTimeout); 
    } 

    [DllImport("kernel32.dll")] 
    static extern int WaitForMultipleObjects(int nCount, IntPtr[] lpHandles, 
     bool bWaitAll, int dwMilliseconds); 
} 

Und installieren Sie die neue Synchronisierungsanbieter mit:

System.ComponentModel.AsyncOperationManager.SynchronizationContext = 
     new MySynchronizationContext(); 

Auf den Punkt gebracht, die SetWaitNotificationRequired () Call teilt der CLR mit, dass sie Ihre Wait() - Überschreibung aufrufen sollte, anstatt ihre eigene Wait() - Implementierung zu verwenden. Und die Wait() - Überschreibung verwendet die Blockierungswartezeit des Betriebssystems, ohne das System anderweitig zu pumpen. Hat gut funktioniert, als ich es getestet habe und das Problem gelöst habe.

+0

Hmm, ich denke, wir würden zuerst versuchen, sich von Barrier.SignalAndWait zu entfernen, und stattdessen einen separaten Thread verwenden, um die Anwendung auszuführen. Wir müssen testen, ob DirectX/OpenGL während der Ausführung der Ereignisschleife angezeigt werden kann. Andernfalls versuchen wir Ihre Herangehensweise. Ich werde deine Antwort akzeptieren, sobald wir mit unseren Tests fertig sind (wahrscheinlich heute Abend). – georch

+0

Okay, wir verwenden jetzt separate Threads für Update, Draw und DoEvents. Das war die einfachere Lösung, und es ermöglicht uns auch andere Aufrufe, die ein STAThread erfordern (wie Cursor.Hide und Cursor.Show, die manchmal während der Laufzeit aufgerufen werden müssen, aber auch andere Aufrufe in der Zukunft). Vielen Dank für die Hilfe! – georch

Verwandte Themen