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"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のドキュメント :
つまり、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();
}
}
ハンドルが作成されていないコントロールの非GUIスレッドからSafeInvoke
を呼び出している場合を考えてください。
uiElement
はnullではないため、uiElement.InvokeRequired
。 MSDNドキュメント(太字) InvokeRequired
は、false
を返します。これは、別のスレッドで作成されたにもかかわらず、ハンドルが作成されていないためです!これにより、 else
を確認するIsDisposed
条件に送信されるか、すぐに送信されたアクションの呼び出しに進みます...バックグラウンドスレッド!
この時点で、すべての賭けはオフになっています。そのコントロールは、2番目の段落で述べたように、メッセージポンプを持たないスレッドでハンドルが作成されているためです。おそらくこれはあなたが遭遇しているケースですか?
InvokeRequired
は信頼できないことがわかったので、単純に
if (!this.IsHandleCreated)
{
this.CreateHandle();
}
ここに私の answer と同様の question があります:
Ithink(まだ完全にはわかりません)これは、コントロールがまだロード/表示されていない場合、InvokeRequiredが常にfalseを返すためです。私は今のところうまくいくと思われる回避策を行いました。それは、その作成者の関連するコントロールのハンドルを簡単に参照することです:
var x = this.Handle;
( http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html を参照)
リンクする投稿のメソッドは、Invoke/BeginInvokeを呼び出してから、コントロールを作成していないスレッドから呼び出された場合にコントロールのハンドルが作成されているかどうかを確認します。
したがって、コントロールを作成したスレッド以外のスレッドからメソッドが呼び出されると、例外が発生します。これは、リモートイベントまたはキューに入れられた作業ユーザーアイテムから発生する可能性があります...
[〜#〜] edit [〜#〜]
Invokeを呼び出す前にInvokeRequiredとHandleCreatedをチェックした場合、その例外は発生しません。
Control
を使用して表示または実行する前に別のスレッドからControl
を使用する場合は、コンストラクター内でハンドルの作成を強制することを検討してください。これは CreateHandle
関数を使用して行われます。
「コントローラー」ロジックがWinFormにないマルチスレッドプロジェクトでは、このエラーを回避するために、この関数はControl
コンストラクターに役立ちます。
次のように、作成者で関連付けられたコントロールのハンドルを参照します。
注:この解決策に注意してください。コントロールにハンドルがある場合、サイズや場所の設定などを行うのは非常に遅くなります。これにより、InitializeComponentが非常に遅くなります。より良い解決策は、コントロールがハンドルを持つ前に何もバックグラウンドにしないことです。
メソッド呼び出しwhile (!this.IsHandleCreated) System.Threading.Thread.Sleep(100)
を呼び出す前にこれを追加してください
これはどうですか :
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;
}
この種の単純なフォームでこの問題が発生しました。
_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; });
}
}
_
今のところうまく機能していると思います...
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();