2017-02-11 76 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

相关问题