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


82

Greg Dがここで説明しているものと同様のSafeInvokeControl拡張メソッドがあります(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"

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でひどいです、誰かがモデル全体とそれを扱う方法を説明する良いシリーズの記事を知っていますか?


1
リンクで何か奇妙なことが起こっています...マークアップとプレビューは正しいです...奇妙です。
ジョージマウアー

Showはどのようなコンテキストで呼び出されますか?たとえば、フォームのコンストラクターから呼び出されたことはありますか?HandleCreatedイベントによってトリガーされたメッセージに対して表示する呼び出しのメッセージをログに記録して、ハンドルが既に作成されているオブジェクトに対してのみshowを呼び出していることを確認すると便利な場合があります。
グレッグD

アプリケーションは何ですか/どのように設計されていますか?this.Show()は何をしますか?(私はそれがこれ以上のことをしていると思います。Visible= true;)ウェブフォームへのあなたの言及はタイプミスですか?
グレッグD

this.Show()は基本のForm.Show()なので、それが何をするにしても。ダイアログがコンストラクターから表示されることはありません。これは、単純なNotify(string)メソッドを持つINotifierサービスの実装によって呼び出されます
George Mauer

4
もう一度見てみると、1年以上経った今、IsHandleCreatedチェックが存在するという理由だけでエラーが発生しているようです。まだ作成されていないコントロールのプロパティを変更しようとしています(メッセージを送信します)。この状況で実行できることの1つは、コントロールの作成前に送信されたデリゲートをキューに入れ、HandleCreatedイベントでそれらを実行することです。
グレッグD

回答:


77

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

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

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

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

これがあなたにとって何を意味するのか見てみましょう。(これは、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スレッドから呼び出している場合を考えてみます。

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

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


EndInvoke後にを含める必要がありますBeginInvokeか?
Odys 2012

@odyodyodys:簡単な答え:いいえ。これは、魔法のような、非常に特殊なケースであり、そうする必要はありません。長い回答:この回答のコメントを読んでください:stackoverflow.com/a/714680/6932
Greg D

1
この回答とMSDNの記事は、Handleが作成されていないためにInvokeRequiredがfalseを返すことに関するものです。ただし、InvokeRequiredがtrueを返した後にBeginvoke / Invokeが呼び出されると、OPで例外が発生します。ハンドルがまだ作成されていない場合、InvokeRequiredはどのようにtrueを返すことができますか?
thewpfguy 2015年

また、IsDisposedに遭遇した競合状態もあります。IsDisposedは、テスト時にfalseになる可能性がありますが、送信されたアクションが完全に実行される前にtrueになります。2つの選択肢は、(a)InvalidOperationExceptionを無視することと、(b)ロックを使用して送信されたアクションとコントロールのdisposeメソッドからクリティカルセクションを作成することのようです。最初はハックのように感じ、2番目は痛みです。
blearyeye 2016年

37

InvokeRequired信頼できないことがわかったので、単に使用します

if (!this.IsHandleCreated)
{
    this.CreateHandle();
}

5
これにより、異なるスレッドで2つのハンドルが作成される可能性がありますか?ハンドルが作成されるはずです、あなただけのイベントのごタイミング/順序を改善する必要が...
デニス・スキッドモア

ニース-私はこれにアクセスするよりもこれを好みます。(a)未使用の変数がなく、(b)何が起こっているのかが明らかであるとして処理します
Dunc

5
MSDN:「コントロールのハンドルがまだ作成されていない場合は、コントロールのプロパティ、メソッド、またはイベントを単に呼び出すべきではありません。これにより、コントロールのハンドルがバックグラウンドスレッドで作成され、コントロールが分離される可能性があります。メッセージポンプのないスレッドで、アプリケーションが不安定になります。」演習の要点は、間違ったスレッドにハンドルを作成しないようにすることです。この呼び出しがGUIスレッドではないスレッドから発生した場合、あなたは死んでいます。
グレッグD

25

これが同様の質問に対する私の答えです:

考えてコントロールがまだロードされていない場合/示すInvokeRequiredが常にfalseを返しますので、これがあることを(まだ完全に確認してください)。私は今のところうまくいくように見える回避策を実行しました。それは、次のように、作成者の関連するコントロールのハンドルを単純に参照することです。

var x = this.Handle; 

http://ikriv.com/en/prog/info/dotnet/MysteriousHang.htmlを参照してください )


ところで非常に興味深い記事。ありがとう。
Yann

おかげで、これは私にとってはうまくいきました。なぜなら、背景のスレッドから出し入れする必要のある隠されたフォームがあったからです。ハンドルを参照することが私にとってそれを機能させたものでした
John Mc

これは、「機能」よりもエラーは少ないものの、最新バージョンの.netでは依然として問題です。オブジェクトに「ウォッチ」を配置してそのプロパティを参照することも、ハンドルを見るのと同じことを行うことに注意してください。あなたはそれを見るとそれが機能するいくつかの量子デバッグのナンセンスになってしまいます。
トニーチータム

5

リンク先の投稿のメソッドはInvoke/を呼び出しますBeginInvokeコントロールのハンドルが、それはコントロールを作成していないスレッドから呼び出されていた場合に作成されているか確認する前に。

そのため、コントロールを作成したスレッド以外のスレッドからメソッドが呼び出されると、例外が発生します。これは、リモートイベントまたはキューに入れられた作業ユーザーアイテムから発生する可能性があります...

編集

チェックしてInvokeRequiredHandleCreatedinvokeを呼び出す前に、その例外が発生しないようにする必要があります。


私が正しく理解していれば、これは、呼び出し元のスレッドがコントロールが作成されたスレッドと異なる場合に発生するということです。イベントがどのスレッドから呼び出されるかは保証できません。それを作成したのは(おそらく)まったく異なるスレッドである可能性があります。これを解決するにはどうすればよいですか?
ジョージマウアー

うん、それは正しい。問題を解決する条件で投稿を編集しました。
Shea

私はこれが事実であるとは確信していません。Arnsheaさんのコメントに基づいて、質問を更新しました。
グレッグD

わかりません。そのウィンドウを表示する必要があります。IsHandleCreatedがfalseである理由はわかりませんが、ウィンドウを表示しないことはオプションではありません。私の質問は、なぜそれがfalseになるのかということです
George Mauer

ハンドルが閉じられているか、コントロールが破棄されている場合、IsHandleCreatedはfalseを返すと思います。以前は存在していたが、現在は存在しないコントロールの非同期呼び出しに噛まれていないことを確信していますか?
グレッグD

3

を使用しControlて他のことを表示または実行する前に、別のスレッドからを使用する場合はControl、コンストラクター内でそのハンドルを強制的に作成することを検討してください。これは、CreateHandle関数実行さます。

「コントローラー」ロジックがWinFormにないマルチスレッドプロジェクトでは、この関数は、Controlこのエラーを回避するためのコンストラクターに役立ちます。



1

次のように、作成者で関連するコントロールのハンドルを参照します。

:この解決策には注意してください。コントロールにハンドルがある場合、そのサイズや場所を設定するなどの操作ははるかに遅くなります。これにより、InitializeComponentがはるかに遅くなります。より良い解決策は、コントロールがハンドルを持つ前に何もバックグラウンドにしないことです。


0

私はこの種の単純な形式でこの問題を抱えていました:

public partial class MyForm : Form
{
    public MyForm()
    {
        Load += new EventHandler(Form1_Load);
    }

    private void Form1_Load(Object sender, EventArgs e)
    {
        InitializeComponent();
    }

    internal void UpdateLabel(string s)
    {
        Invoke(new Action(() => { label1.Text = s; }));
    }
}

その後のためにn他の非同期スレッド私が使っていたnew MyForm().UpdateLabel(text)試してみて、UIスレッドを呼び出すことが、他のスレッドはどちらかである他のインスタンスハンドル、取得するので、コンストラクタは、UIスレッドのインスタンスへのハンドルを与えないObject reference not set to an instance of an objectかをInvoke or BeginInvoke cannot be called on a control until the window handle has been created。これを解決するために、静的オブジェクトを使用してUIハンドルを保持しました。

public partial class MyForm : Form
{
    private static MyForm _mf;        

    public MyForm()
    {
        Load += new EventHandler(Form1_Load);
    }

    private void Form1_Load(Object sender, EventArgs e)
    {
        InitializeComponent();
        _mf = this;
    }

    internal void UpdateLabel(string s)
    {
        _mf.Invoke((MethodInvoker) delegate { _mf.label1.Text = s; });
    }
}

これまでのところ、正常に機能していると思います...


0
var that = this; // this is a form
(new Thread(()=> {

    var action= new Action(() => {
       something
    }));

    if(!that.IsDisposed)
    {
        if(that.IsHandleCreated)
        {
            //if (that.InvokeRequired)
                that.BeginInvoke(action);
            //else
            // action.Invoke();
        }
        else
            that.HandleCreated+=(sender,event) => {
                action.Invoke();
            };
    }


})).Start();

これはc#です-this呼び出しによって変化することはありません。JavaScriptスタイルの手法は不要です。
ジョージマウアー

何を呼び出すかを明示的にしようとしました。-何でも
ShimonDoodkin19年

0

これはどうですか :


    public static bool SafeInvoke( this Control control, MethodInvoker method )
    {
        if( control != null && ! control.IsDisposed && control.IsHandleCreated && control.FindForm().IsHandleCreated )
        {
            if( control.InvokeRequired )
            {
                control.Invoke( method );
            }
            else
            {
                method();
            }
            return true;
        }
        else return false;
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.