在维护严重违反winforms中的跨线程更新规则的较旧应用程序的过程中,我创建了以下扩展方法,以便在发现非法调用后迅速对其进行修复:
/// <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必须为真:
string taskName = string.Empty; this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true);
我不怀疑这种方法在修复旧版代码中的非法调用方面是否有用,但是新代码呢?
当您可能不知道哪个线程正在尝试更新ui时,使用此方法来更新新软件中的UI是很好的设计吗?或者新的Winforms代码通常应包含特定的,专用的方法,并具有与之Invoke()相关的适当管道吗?所有此类UI更新?(当然,我将首先尝试使用其他适当的后台处理技术,例如BackgroundWorker。)
Invoke()
有趣的是,这不适用于ToolStripItems。我最近才发现它们直接来自Component而不是Control。而是ToolStrip应使用contains的invoke。
ToolStrip
一些评论建议:
if (uiElement.InvokeRequired)
应该:
if (uiElement.InvokeRequired && uiElement.IsHandleCreated)
考虑以下msdn文档:
这意味着如果不需要Invoke(调用发生在同一线程上),或者 如果控件是在其他线程上创建的,但尚未创建控件的句柄 ,则InvokeRequired可以 返回false 。 如果尚未创建控件的句柄,则不应简单地在控件上调用属性,方法或事件。这可能会导致在后台线程上创建控件的句柄,从而在没有消息泵的情况下将控件隔离在线程上,并使应用程序不稳定。 通过在InvokeRequired在后台线程上返回false时还检查IsHandleCreated的值,可以防止这种情况的发生。
这意味着如果不需要Invoke(调用发生在同一线程上),或者 如果控件是在其他线程上创建的,但尚未创建控件的句柄 ,则InvokeRequired可以 返回false 。
如果尚未创建控件的句柄,则不应简单地在控件上调用属性,方法或事件。这可能会导致在后台线程上创建控件的句柄,从而在没有消息泵的情况下将控件隔离在线程上,并使应用程序不稳定。
通过在InvokeRequired在后台线程上返回false时还检查IsHandleCreated的值,可以防止这种情况的发生。
如果控件是在其他线程上创建的,但尚未创建控件的句柄,则InvokeRequired返回false。这意味着,如果InvokeRequiredreturn true,IsHandleCreated将始终为true。再次测试是多余且不正确的。
InvokeRequired
true
IsHandleCreated
我喜欢总体思路,但确实看到了一个问题。处理EndInvokes很重要,否则可能会导致资源泄漏。我知道很多人不相信这一点,但这确实是事实。
这是一个谈论它的链接。还有其他。
但是我的主要答复是:是的,我认为您在这里有了一个不错的主意。