是否适当扩展控制提供一贯安全的调用/ BeginInvokefunction?

在维护一个严重违反winform中的跨线程更新规则的旧应用程序的过程中,我创build了以下扩展方法,以便在发现它们时快速修复非法呼叫:

/// <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软件是否是一个好的devise,或者如果新的Winforms代码通常包含一个特定的专用方法和适当的Invoke()所有这些UI更新相关的pipe道? (我会尝试先使用其他适当的后台处理技术,当然,例如BackgroundWorker)。

有趣的是,这对ToolStripItems不起作用。 我刚刚发现他们直接从组件而不是从控制派生。 应该使用包含ToolStrip的invoke。

跟进评论:

有些意见build议:

 if (uiElement.InvokeRequired) 

应该:

 if (uiElement.InvokeRequired && uiElement.IsHandleCreated) 

考虑下面的msdn文档 :

这意味着,如果Invoke不是必需的(调用发生在同一线程上),或者如果控件是在不同的线程上创build的,但控件的句柄尚未创build ,则InvokeRequired可以返回false

在控件的句柄尚未创build的情况下,不应该简单地调用控件上的属性,方法或事件。 这可能会导致在后台线程上创build控件的句柄,在没有消息泵的情况下将线程上的控件隔离,并使应用程序不稳定。

当InvokeRequired在后台线程上返回false时,可以通过检查IsHandleCreated的值来防止这种情况。

如果控件是在不同的线程上创build的,但控件的句柄尚未创build,则InvokeRequired返回false。 这意味着如果InvokeRequired返回trueIsHandleCreated将始终为真。 再次testing是多余的和不正确的。

我喜欢一般的想法,但我确实看到一个问题。 处理EndInvokes是很重要的,或者你可能有资源泄漏。 我知道很多人不相信这一点,但是确实如此。

这里有一个链接在谈论它 。 还有其他的。

但我的主要回应是:是的,我认为你在这里有一个好主意。

你也应该创buildBegin和End扩展方法。 如果你使用generics,你可以使电话看起来更好一点。

 public static class ControlExtensions { public static void InvokeEx<T>(this T @this, Action<T> action) where T : Control { if (@this.InvokeRequired) { @this.Invoke(action, new object[] { @this }); } else { if (!@this.IsHandleCreated) return; if (@this.IsDisposed) throw new ObjectDisposedException("@this is disposed."); action(@this); } } public static IAsyncResult BeginInvokeEx<T>(this T @this, Action<T> action) where T : Control { return @this.BeginInvoke((Action)delegate { @this.InvokeEx(action); }); } public static void EndInvokeEx<T>(this T @this, IAsyncResult result) where T : Control { @this.EndInvoke(result); } } 

现在你的电话会变得更短,更清洁:

 this.lblTimeDisplay.InvokeEx(l => l.Text = this.task.Duration.ToString()); var result = this.BeginInvokeEx(f => f.Text = "Different Title"); // ... wait this.EndInvokeEx(result); 

关于Component ,只需调用窗体或容器本身。

 this.InvokeEx(f => f.toolStripItem1.Text = "Hello World"); 

这实际上不是一个答案,但回答了接受的答案的一些意见。

对于标准的IAsyncResult模式, BeginXXX方法包含了AsyncCallback参数,所以如果你想说“我不在乎这个 – 只要在完成时调用EndInvoke并忽略结果”,你可以做这样的事情(这是为了Action但应该能够调整为其他代表types):

  ... public static void BeginInvokeEx(this Action a){ a.BeginInvoke(a.EndInvoke, a); } ... // Don't worry about EndInvoke // it will be called when finish new Action(() => {}).BeginInvokeEx(); 

(不幸的是我没有一个解决scheme,没有每次使用这种模式时都没有声明variables的帮助函数)。

但是对于Control.BeginInvoke我们没有AsyncCallBack ,所以没有简单的方法用Control.EndInvoke保证被调用。 它的devise方式提示Control.EndInvoke是可选的。