none
ハンドル作成済みのFormへのInvoke実行時に「ウィンドウ ハンドルが作成される前に~」というエラーが発生する RRS feed

  • 質問

  • 初投稿です。nogu611と申します。

    対処方法がわからないエラーが発生しており、困っております。
    質問させてください。

    自社の製品で処理の進捗を表示するFormがあるのですが、Windows7を利用する特定のユーザー環境だけでエラーが頻発します。
    開発環境では再現できないのですが、進捗表示のFormを表示/非表示を繰り返すと、少々強引ですが現象を再現することができました。
    再現ソースとエラーの詳細は以下となります。

    再現ソースでは、進捗表示Formの「HandleCreatedイベント」発生以降にInvoke()を呼び出しているので、
    既にウィンドウハンドルは作成されているのではないかと思うのですが、
    「ウィンドウハンドルが作成される前、・・・」というエラーが発生してしまいます。
    この動作はFrameworkの仕様なのでしょうか?

    それとも、コード側に問題があるのでしょうか?
    (Application.Run()を複数呼び出しているところが気になっております。複数呼び出すのはNGという記事はみつけられませんでした。)


    捕捉

    再現プログラムでは、他のウィンドウの後ろに進捗表示Formを表示するとエラーが発生しやすくなるようです。
    WindowsXP環境では再現できませんでした。


    ■再現環境

      Windows7 Professional
      プロセッサ: Intel(R) Core(TM)2 Duo CPU E8400 @ 3.00GHz 3.00GHz
      実装メモリ(RAM): 2.00GB
      システムの種類: 32ビットオペレーティングシステム

    ■エラー詳細

    ************** 例外テキスト **************
    System.InvalidOperationException: ウィンドウ ハンドルが作成される前、コントロールで Invoke または BeginInvoke を呼び出せません。
       場所 System.Windows.Forms.Control.WaitForWaitHandle(WaitHandle waitHandle)
       場所 System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
       場所 System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
       場所 System.Windows.Forms.Control.Invoke(Delegate method)
       場所 ShowProgress.ProgressForm.CloseProgress() 場所 C:\XXXX\ShowProgress\ProgressForm.cs:行 46
       場所 ShowProgress.Form1.button1_Click(Object sender, EventArgs e) 場所 C:\XXXX\ShowProgress\Form1.cs:行 23
       場所 System.Windows.Forms.Control.OnClick(EventArgs e)
       場所 System.Windows.Forms.Button.OnClick(EventArgs e)
       場所 System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
       場所 System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
       場所 System.Windows.Forms.Control.WndProc(Message& m)
       場所 System.Windows.Forms.ButtonBase.WndProc(Message& m)
       場所 System.Windows.Forms.Button.WndProc(Message& m)
       場所 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       場所 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       場所 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

    ************** 読み込まれたアセンブリ **************
    mscorlib
        アセンブリ バージョン: 2.0.0.0
        Win32 バージョン: 2.0.50727.4952 (win7RTMGDR.050727-4900)
        コードベース: file:///C:/Windows/Microsoft.NET/Framework/v2.0.50727/mscorlib.dll
    ----------------------------------------
    ShowSplash
        アセンブリ バージョン: 1.0.0.0
        Win32 バージョン: 1.0.0.0
        コードベース: file://XXXX/共有/ShowProgress.exe
    ----------------------------------------
    System.Windows.Forms
        アセンブリ バージョン: 2.0.0.0
        Win32 バージョン: 2.0.50727.4927 (NetFXspW7.050727-4900)
        コードベース: file:///C:/Windows/assembly/GAC_MSIL/System.Windows.Forms/2.0.0.0__b77a5c561934e089/System.Windows.Forms.dll
    ----------------------------------------
    System
        アセンブリ バージョン: 2.0.0.0
        Win32 バージョン: 2.0.50727.4927 (NetFXspW7.050727-4900)
        コードベース: file:///C:/Windows/assembly/GAC_MSIL/System/2.0.0.0__b77a5c561934e089/System.dll
    ----------------------------------------
    System.Drawing
        アセンブリ バージョン: 2.0.0.0
        Win32 バージョン: 2.0.50727.4927 (NetFXspW7.050727-4900)
        コードベース: file:///C:/Windows/assembly/GAC_MSIL/System.Drawing/2.0.0.0__b03f5f7f11d50a3a/System.Drawing.dll
    ----------------------------------------
    mscorlib.resources
        アセンブリ バージョン: 2.0.0.0
        Win32 バージョン: 2.0.50727.4952 (win7RTMGDR.050727-4900)
        コードベース: file:///C:/Windows/Microsoft.NET/Framework/v2.0.50727/mscorlib.dll
    ----------------------------------------
    System.Windows.Forms.resources
        アセンブリ バージョン: 2.0.0.0
        Win32 バージョン: 2.0.50727.4927 (NetFXspW7.050727-4900)
        コードベース: file:///C:/Windows/assembly/GAC_MSIL/System.Windows.Forms.resources/2.0.0.0_ja_b77a5c561934e089/System.Windows.Forms.resources.dll
    ----------------------------------------

    ■ソース

      public partial class ProgressForm : Form
      {
        private ProgressForm()
        {
          InitializeComponent();
        }
    
        private static ProgressForm _form = null;
        private static Thread _thread = null;
    
        private static ManualResetEvent _waitCreateHandle = new ManualResetEvent(false);
    
        public static void ShowProgress()
        {
          if (_form != null || _thread != null)
            return;
    
          _waitCreateHandle.Reset();
    
          _thread = new System.Threading.Thread(
            new System.Threading.ThreadStart(StartThread));
          _thread.Name = "SplashForm";
          _thread.IsBackground = true;
    
          _thread.Start();
        }
    
        public static void CloseProgress()
        {
          _waitCreateHandle.WaitOne();
    
          if (_form != null && _form.IsDisposed == false)
          {
            if (_form.InvokeRequired)
              _form.Invoke(new MethodInvoker(_form.Close));
            else
              _form.Close();
          }
    
          _form = null;
          _thread = null;
        }
    
        private static void StartThread()
        {
          _form = new ProgressForm();
          _form.HandleCreated += new EventHandler(SplashForm_HandleCreated);
          Application.Run(_form);
        }
    
        private static void SplashForm_HandleCreated(object sender, EventArgs e)
        {
          _waitCreateHandle.Set();
        }
    
      }
    
      public partial class Form1 : Form
      {
        public Form1()
        {
          InitializeComponent();
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
    	  //表示非表示を繰り返すとエラー発生
          for (int i = 0; i < 100000; i++)
          {
            ProgressForm.ShowProgress();
            ProgressForm.CloseProgress();
          }
        }
      }
    
      static class Program
      {
        [STAThread]
        static void Main()
        {
          Form1 mainForm = new Form1();
          Application.Run(mainForm);
        }
    
      }
    

    2010年12月28日 1:42

回答

  • まず、Windows Forms では、マルチ UI スレッドは考慮されていない部分が多少あるので、可能であれば UI スレッドは1本にしたほうがよいです。たとえば、ここで利用されている Control.Invoke はバックグラウンドスレッドから UI スレッドを呼び出すために利用されることを目的として作成されており、UI スレッドから別の UI スレッドを呼び出すようには設計されていません。(デッドロックします)

    次に、フォームを閉じられるようになるタイミングは、ハンドルの生成とは厳密には異なると思います。ウィンドウハンドルの生成はフォームの生成処理の中の一部分でしかないため、その時点で Control.Invoke が完全に準備されるとは限らないでしょう。(このフォームの例であれば、Shown イベントでも十分ではないかと思います)

    バックグラウンドスレッドでは困る原因があって、どうしても UI スレッドを分割しなければならない場合、Close を呼ぶかわりに ProgressForm 側の ApplicationContext に対して通知を行って ApplicationContext で ExitThread を呼ぶようにすると解決するのではないか?と思います。

    • 回答としてマーク nogu611 2010年12月28日 6:22
    2010年12月28日 3:37

すべての返信

  • まず、Windows Forms では、マルチ UI スレッドは考慮されていない部分が多少あるので、可能であれば UI スレッドは1本にしたほうがよいです。たとえば、ここで利用されている Control.Invoke はバックグラウンドスレッドから UI スレッドを呼び出すために利用されることを目的として作成されており、UI スレッドから別の UI スレッドを呼び出すようには設計されていません。(デッドロックします)

    次に、フォームを閉じられるようになるタイミングは、ハンドルの生成とは厳密には異なると思います。ウィンドウハンドルの生成はフォームの生成処理の中の一部分でしかないため、その時点で Control.Invoke が完全に準備されるとは限らないでしょう。(このフォームの例であれば、Shown イベントでも十分ではないかと思います)

    バックグラウンドスレッドでは困る原因があって、どうしても UI スレッドを分割しなければならない場合、Close を呼ぶかわりに ProgressForm 側の ApplicationContext に対して通知を行って ApplicationContext で ExitThread を呼ぶようにすると解決するのではないか?と思います。

    • 回答としてマーク nogu611 2010年12月28日 6:22
    2010年12月28日 3:37
  • Application.Run が何回も呼ばれるのは、やはりよろしくないと思います。

     Calling Application.Run twice causing odd behavior
     http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/aead5e9a-2e06-4adf-aecc-f45a2a633768

    特定の PC でしか現象が起こらないのは、OS とかマルチ コアとか、スレッドの動きが異なるからでしょうか。
    2010年12月28日 3:42
  • K.Takaoka様 返信ありがとうございます。

    まず、Windows Forms では、マルチ UI スレッドは考慮されていない部分が多少あるので、可能であれば UI スレッドは1本にしたほうがよいです。たとえば、ここで利用されている Control.Invoke はバックグラウンドスレッドから UI スレッドを呼び出すために利用されることを目的として作成されており、UI スレッドから別の UI スレッドを呼び出すようには設計されていません。(デッドロックします)

    やはりマルチUIスレッドはあまりよくないのですね。 勉強不足でした。

    次に、フォームを閉じられるようになるタイミングは、ハンドルの生成とは厳密には異なると思います。ウィンドウハンドルの生成はフォームの生成処理の中の一部分でしかないため、その時点で Control.Invoke が完全に準備されるとは限らないでしょう。(このフォームの例であれば、Shown イベントでも十分ではないかと思います)

    今回のサンプルはFormのハンドルが作られているはずなのに上記のような例外が発生しますといいたかったので、あえてHandleCreatedイベントで実装してみました。これとは別にForm.Activated、Form.Shownでも同様に試してみましたが、やはり同様の例外がおきました。

    バックグラウンドスレッドでは困る原因があって、どうしても UI スレッドを分割しなければならない場合、Close を呼ぶかわりに ProgressForm 側の ApplicationContext に対して通知を行って ApplicationContext で ExitThread を呼ぶようにすると解決するのではないか?と思います。

    メインのUIスレッド側でビジネスロジックを実装しているので、どうしてもProgressFormは別スレッドで表示を行いたいのです。 また、このProgressFormは製品共通で利用しているもので、組み込み箇所が多すぎて利用者側の修正は不能なので、 できれば別スレッドで同じように表示する方向で対処を行いたいです。 K.Takaoka様のおっしゃるとおりApplicationContext.ExitThreadを利用してProgressFormを閉じるように修正を行うとエラーが発生しなくなりました。ありがとうございます。修正したサンプルコードを以下に載せておきます。

    ■修正後コード

      public partial class ProgressForm : Form
      {
        private ProgressForm()
        {
          InitializeComponent();
        }
    
        private static ApplicationContext _appContext = null;
    
        private static ManualResetEvent _waitCreateHandle = new ManualResetEvent(false);
    
        public static void ShowProgress()
        {
          if (_appContext != null)
          {
            return;
          }
    
          _waitCreateHandle.Reset();
    
          Thread _thread = new System.Threading.Thread(
            new System.Threading.ThreadStart(StartThread));
          _thread.Name = "SplashForm";
          _thread.IsBackground = true;
    
          _thread.Start();
        }
    
        public static void CloseProgress()
        {
          _waitCreateHandle.WaitOne();
    
          _appContext.ExitThread();
    
          _appContext = null;
        }
    
        private static void StartThread()
        {
          Form _form = new ProgressForm();
          _form.Shown += new EventHandler(ProgressForm_Shown);
    
          _appContext = new ApplicationContext();
          _appContext.MainForm = _form;
    
          Application.Run(_appContext);
        }
    
        private static void ProgressForm_Shown(object sender, EventArgs e)
        {
          _waitCreateHandle.Set();
        }
    
      }
    

    2010年12月28日 5:20
  • totojo様 回答ありがとうございます。

     

    参考URL確認させていただきました。

    参考URLのように、バックグラウンドスレッドからメインフォームにInvokeして、メインスレッド側でProgressFormを表示するようにサンプルを修正して試してみましたがうまくFormが表示できませんでした。

     

    また、別スレッドを作らず、単純にProgressForm.Show()/Close()するように修正してみましたが、ProgressFormがうまく描画されず,進捗表示用Formとしての用件を満たせませんでした。

     

    実際の製品では、メインのUIスレッド側でProgressForm表示中はビジネスロジックをゴリゴリ実行しています。

    そのため、メッセージがうまく流れず表示がうまくいかないのだと思います。

    また、ProgressFormは製品共通で利用しているクラスで、利用箇所が多いためApplication.DoEvents()をビジネスロジックを差し込んでゆくのも厳しいです。

     

    検討させていただいた結果K.Takaoka様の案で対応させていただこうと思います。

    有難うございました。

    2010年12月28日 6:10
  • 特定の PC でしか現象が起こらないのは、OS とかマルチ コアとか、スレッドの動きが異なるからでしょうか。

    そうなのかもしれませんね。現象がでるのがユーザーのPCのみということが多くて困ります。

     

    ちょっと調べてみたのですが、現象がでないXP環境(開発環境)と検証したWindows7環境のSystem.Windows.Formsのアセンブリはバージョンが異なりました。

    ファイルバージョン

    ・XP

     2.0.50727.3053

    ・Windows7

      2.0.50727.4927

    Refrectorでちょっとのぞかせてもらったのですが、例外が発生しているControl.WaitForWaitHandle()はXPのものとWindows7のもので中のロジックが異なるようでした。このへんの違いも現象が出る出ないに関係するのかもしれないですね。

    2010年12月28日 6:21