2017-02-11 10 views
4

私たちはC#でマルチスレッドのゲームエンジンを開発していますが、STAThread属性が必要(またはSTAを手動でSTAに設定する) -dropサポート(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ミリ秒間ブロックして1回だけポンピングするのでオッズがかなり高いです。いくつかの不思議な理由のために、その後のメッセージは発送されません。ほぼ確実に、そのメッセージループによって行われたフィルタリングによって引き起こされます。そうでなければ見えにくく、そのコードは公開されず、逆コンパイルも不可能でした。

修正は可能ですが、容易ではありません。この問題を回避するには、CLRをに強制しないでください。はこのメッセージループを励起します。あなたの待ち時間が短いので大丈夫です。 SynchronizationContext.Wait()メソッドをオーバーライドすることでできること。残念ながらそれは難しいですが、WindowsFormsSynchronizationContextクラスは密閉されているため、Wait()メソッドをオーバーライドすることはできません。 Reference Sourceにアクセスし、クラスにコードをコピー/ペーストする必要があります。別の名前をつけてください。

私はあなたが変更する必要があるものを示し、それの短いバージョンをあげる:(一言で言えば

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

、SetWaitNotificationRequired:

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

をとした新しい同期プロバイダーをインストール)呼び出しは、独自のWait()実装を使用する代わりに、Wait()オーバーライドを呼び出す必要があることをCLRに通知します。また、Wait()オーバーライドは、OSのブロッキング待機を使用します。私はそれをテストし、問題を解決したときにうまくいった。

+0

ええと、まず、Barrier.SignalAndWaitから離れて、別のスレッドを使用してアプリケーションを実行してみましょう。イベントループが実行されている間、DirectX/OpenGLが存在するかどうか試してみる必要があります。それ以外の場合は、あなたのアプローチを試みます。私たちのテストが終わったらすぐにあなたの答えを受け入れます(つまり、今晩)。 – georch

+0

さて、更新、描画、およびDoEventsに別々のスレッドを使用するようになりました。それは簡単な解決策でした。また、(実行時に時々呼び出されるCursor.HideやCursor.Showのように、また将来は他の呼び出しでも)STAThreadを必要とする他の呼び出しを行うこともできます。助けてくれてありがとう! – georch

関連する問題