winform のクロススレッド更新規則にひどく違反していた古いアプリケーションのメンテナンス中に、不正な呼び出しを発見したときにすばやく修正する方法として、次の拡張メソッドを作成しました。
/// <summary>
/// Execute a method on the control's owning thread.
/// </summary>
/// <param name="uiElement">The control that is being updated.</param>
/// <param name="updater">The method that updates uiElement.</param>
/// <param name="forceSynchronous">True to force synchronous execution of
/// updater. False to allow asynchronous execution if the call is marshalled
/// from a non-GUI thread. If the method is called on the GUI thread,
/// execution is always synchronous.</param>
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.IsHandleCreated)
{
// Do nothing if the handle isn't created already. The user's responsible
// for ensuring that the handle they give us exists.
return;
}
if (uiElement.IsDisposed)
{
throw new ObjectDisposedException("Control is already disposed.");
}
updater();
}
}
使用例:
this.lblTimeDisplay.SafeInvoke(() => this.lblTimeDisplay.Text = this.task.Duration.ToString(), false);
クロージャを活用して読み取る方法も気に入っていますが、その場合は forceSynchronous を true にする必要があります。
string taskName = string.Empty;
this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true);
レガシー コードの不正な呼び出しを修正するためのこのメソッドの有用性については疑問ではありませんが、新しいコードについてはどうでしょうか。
UI を更新しようとしているスレッドがわからない場合に、このメソッドを使用して新しいソフトウェアの UI を更新するのは良い設計ですか? または、新しい Winforms コードには通常、適切なInvoke()
関連の配管を備えた特定の専用メソッドが含まれている必要があります。そのようなすべてのUIの更新?(もちろん、BackgroundWorker など、他の適切なバックグラウンド処理手法を最初に使用しようとします。)
興味深いことに、これはToolStripItemsでは機能しません。最近、それらがControlではなくComponentから直接派生していることを発見しました。代わりに、包含の呼び出しを使用する必要があります。ToolStrip
コメントへのフォローアップ:
一部のコメントは、次のことを示唆しています。
if (uiElement.InvokeRequired)
次のようにする必要があります。
if (uiElement.InvokeRequired && uiElement.IsHandleCreated)
次のmsdn ドキュメントを検討してください。
つまり、InvokeRequired は 、Invoke が必要でない (呼び出しが同じスレッドで発生する)場合、またはコントロールが別のスレッドで作成されたが、コントロールのハンドルがまだ作成されていない場合に、 false を返すことができます。
コントロールのハンドルがまだ作成されていない場合は、コントロールのプロパティ、メソッド、またはイベントを単純に呼び出すべきではありません。これにより、コントロールのハンドルがバックグラウンド スレッドで作成され、メッセージ ポンプのないスレッドでコントロールが分離され、アプリケーションが不安定になる可能性があります。
バックグラウンド スレッドで InvokeRequired が false を返したときに IsHandleCreated の値も確認することで、このケースを防ぐことができます。
コントロールが別のスレッドで作成されたが、コントロールのハンドルがまだ作成されていない場合は、 InvokeRequired
false を返します。これは、 if InvokeRequired
returns true
,IsHandleCreated
が常に true になることを意味します。再度テストすることは冗長であり、正しくありません。