【发布时间】:2010-10-17 09:53:24
【问题描述】:
在维护严重违反 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 代码通常应该包含一个特定的、专用的方法和适当的 @所有此类 UI 更新的 987654328@ 相关管道? (当然,我会先尝试使用其他合适的后台处理技术,例如 BackgroundWorker。)
有趣的是,这不适用于ToolStripItems。我最近才发现它们直接来自Component,而不是来自Control。相反,应该使用包含 ToolStrip 的调用。
对 cmets 的跟进:
一些 cmets 建议:
if (uiElement.InvokeRequired)
应该是:
if (uiElement.InvokeRequired && uiElement.IsHandleCreated)
考虑以下msdn documentation:
这意味着 InvokeRequired 可以 返回 false 如果不需要调用 (调用发生在同一个线程上), 或 如果控件是在 不同的线程,但控件的 尚未创建句柄。
在控件的句柄的情况下 尚未创建,您应该 不仅仅是调用属性、方法, 或控件上的事件。这有可能 导致控件的句柄是 在后台线程上创建, 隔离线程上的控件 没有消息泵并制作 应用不稳定。
您可以通过以下方式防范这种情况 还检查的值 IsHandleCreated when InvokeRequired 在后台线程上返回 false。
如果控件是在不同的线程上创建的,但尚未创建控件的句柄,InvokeRequired 将返回 false。这意味着如果InvokeRequired 返回true,IsHandleCreated 将始终为真。再次测试它是多余且不正确的。
【问题讨论】:
-
+1 问题 - 我发现自己一直在为主 UI 线程上的事物编写调用回调。如果扩展方法不包含太多缺点,这将是一个巨大的节省时间。
-
我会把它重命名为“SafeInvoke”
-
@Joel:这个名字更好。更新问题以反映建议。 (我从来没有真正喜欢过“UpdateUI”这个名字。)
-
我总是将 ToolStripItem 设为静态以解决该特定问题。
-
@Lii: (1) 你可以做很多事情。如果你更喜欢抛出异常,那就去吧。 (2) 当我们到达
BeginInvoke()/Invoke()时,我们还没有验证所有要求的先决条件。例如,我们尚未验证句柄是否存在。直接调用updater会回避验证。
标签: c# winforms controls extension-methods invoke