1. ホーム
  2. winforms

[解決済み] ウィンドウハンドルが作成されるまで、コントロールに対してInvokeまたはBeginInvokeを呼び出すことはできません。

2023-06-03 16:54:28

質問

次のような SafeInvoke Control 拡張メソッドを持っています。 Greg D がここで説明している (IsHandleCreated チェックを除いたもの) に似ています。

から呼び出しています。 System.Windows.Forms.Form を以下のように呼び出しています。

public void Show(string text) {
    label.SafeInvoke(()=>label.Text = text);
    this.Show();
    this.Refresh();
}

時々(この呼び出しは様々なスレッドから来る可能性があります)、これは次のようなエラーになります。

System.InvalidOperationException が発生

Message = "ウィンドウハンドルが作成されるまで、InvokeまたはBeginInvokeはコントロール上で呼び出すことができません。

Source = "System.Windows.Forms".System.Windows.Forms"。

StackTrace:
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.Invoke(Delegate method)
at DriverInterface2.UI.WinForms.Dialogs.FormExtensions.SafeInvoke[T](T control, Action`1 action) 
in C:\code\DriverInterface2\DriverInterface2.UI.WinForms\Dialogs\FormExtensions.cs:line 16

何が起こっていて、どうすれば直るのでしょうか? 一度うまくいって、次に失敗することがあるので、フォーム作成の問題でないことは十分承知していますが、問題は何でしょうか?

PS. 私は本当に本当にWinFormsでひどいです、誰かモデル全体とそれを使用する方法を説明する良い記事のシリーズを知っていますか?

どのように解決するのですか?

間違ったスレッドでコントロールを作成している可能性があります。 次のように考えてください。 MSDN のドキュメント :

つまり、InvokeRequiredは が false を返すということです。 (呼び出しは同じスレッドで発生します)。 あるいは コントロールが別のスレッドで作成された場合 コントロールが別のスレッドで作成されたが、そのコントロールの ハンドルがまだ作成されていない場合。

コントロールのハンドル がまだ作成されていない場合は コントロールのプロパティ、メソッド、イベントを単純に呼び出すことはできません。 またはイベントを呼び出すべきではありません。これは コントロールのハンドルがバックグラウンドスレッドで作成され をバックグラウンドスレッドで作成することになります。 メッセージポンプのないスレッドでコントロールを孤立させ メッセージ・ポンプのないスレッドでコントロールが孤立し アプリケーションを不安定にします。

このケースを防ぐには の値もチェックすることで、このケースを防ぐことができます。 をチェックすることで、このケースを防ぐことができます。 が false を返したとき、バックグラウンドスレッドで コントロールハンドルがまだ作成されていない場合 コントロール・ハンドルがまだ作成されていない場合は、作成されるのを待ってから を呼び出す前に、それが作成されるまで待たなければならない。 BeginInvokeを呼び出す前に、作成されるまで待つ必要があります。一般的に、これは バックグラウンドスレッドが主フォームのコンストラクタで作成された場合 のコンストラクタでバックグラウンドスレッドが作成される場合のみです。 のコンストラクタでバックグラウンド・スレッドが作成された場合のみ発生します (例 Application.Run(new MainForm()) のように)。 フォームが表示される前、あるいは Application.Run が呼び出される前に、アプリケーションのプライマリフォームのコンストラクタでバックグラウンドスレッドが作成される場合のみです。

これがあなたにとって何を意味するのか見てみましょう。 (これは、SafeInvokeの実装も見ていれば、推論しやすいでしょう)

に対するチェックを除いて、あなたの実装が参照されたものと同一であると仮定します。 IsHandleCreated に対するチェックを除いて、参照されたものと同じであると仮定して、ロジックを追ってみましょう。

public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
    if (uiElement == null)
    {
        throw new ArgumentNullException("uiElement");
    }

    if (uiElement.InvokeRequired)
    {
        if (forceSynchronous)
        {
            uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
        else
        {
            uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
    }
    else
    {    
        if (uiElement.IsDisposed)
        {
            throw new ObjectDisposedException("Control is already disposed.");
        }

        updater();
    }
}

を呼び出す場合を考えてみましょう。 SafeInvoke を非GUIスレッドから呼び出している場合を考えてみましょう。

uiElement はnullではないので、チェックするのは uiElement.InvokeRequired . MSDN のドキュメントによると (太字) InvokeRequired false なぜなら、別のスレッドで作成されたにもかかわらず、ハンドルが作成されていないからです! これは私たちを else をチェックすることになります。 IsDisposed をチェックするか、すぐに送信されたアクションの呼び出しに進みます... バックグラウンドスレッドから !

この時点では、2 番目の段落で述べたように、そのハンドルはメッセージ ポンプを持たないスレッドで作成されているため、そのコントロールに関するすべての賭けが外されています。 おそらくこれは、あなたが遭遇しているケースです。