2017-02-11 74 views
4

我們正在C#中開發一個多線程遊戲引擎,並且我們遇到了需要STAThread屬性(或手動將我們的線程設置爲STA)以啓用拖動的問題以及支持(AllowDrop不能在沒有STA的情況下設置)。但是,當我們啓用STA並且更新方法比draw方法花費的時間更長(如下所示)時,窗口不會再正常工作 - 當它在任務欄中單擊時,它不會像您期望的那樣最小化和最大化它。不同系統的確切行爲是不同的,我猜想這裏會出現某種競爭條件。當使用STA和線程花費太長時間表單行爲異常

下面是我們的測試代碼:

[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(); 
     } 
    } 

回答

5

我認識到這個問題,我在非託管的應用程序調試一個非常類似的問題早在Vista的日子。這個問題很模糊,與你通過點擊任務欄按鈕生成的非常氣質的WM_ACTIVATEAPP消息有關。特別是當它由嵌套的消息循環調度並且該循環執行消息過濾以僅允許發送某些「安全」消息時。

它是由Barrier.SignalAndWait()調用引起的。這阻止了UI線程,並且這對於STA線程是非法的。 CLR做了一些有關它的事情,當底層的同步對象沒有被髮送信號時,它將自己的消息循環泵出來。如果它是循環調度WM_ACTIVATEAPP,並且由於您阻塞30毫秒並且只泵一次而可能性很高,那麼它會出錯。由於一些神祕的原因,後續的消息不會被分派。幾乎肯定是由該消息循環完成的過濾造成的。否則很難看到,該代碼從未發佈,也無法反編譯。

它是可以修復的,但不容易。解決方法是強制CLR到而不是泵這個消息循環。沒關係,因爲你的等待時間很短。你可以通過重寫SynchronizationContext.Wait()方法來做些什麼。不幸的是,這很難做到,WindowsFormsSynchronizationContext類是密封的,所以不能派生出來覆蓋它的Wait()方法。您需要訪問Reference Source並將該類複製/粘貼到您的代碼中。給它另一個名字。

我給它的短版,顯示你需要改變什麼:

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); 
} 

並與安裝新的同步提供者:

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

簡而言之,該SetWaitNotificationRequired( )調用告訴CLR它應該調用Wait()重寫,而不是使用它自己的Wait()實現。 Wait()覆蓋使用操作系統的阻塞等待,而不用抽取。當我測試並解決問題時工作得很好。

+0

嗯,我想我們會先嚐試擺脫Barrier.SignalAndWait,然後使用單獨的線程來運行應用程序。我們必須嘗試在事件循環運行時DirectX/OpenGL是否可以呈現。否則,我們會嘗試你的方法。只要我們完成了我們的測試,我會盡快接受你的回答(即可能在今天晚上)。 – georch

+0

好的,我們現在使用單獨的線程進行更新,繪製和DoEvents。這是更簡單的解決方案,它還允許我們進行需要STAThread的其他調用(如Cursor.Hide和Cursor.Show,有時需要在運行時調用Cursor.Hide和Cursor.Show,以及將來調用其他調用)。非常感謝您的幫助! – georch

相關問題