当前位置:  开发笔记 > 编程语言 > 正文

是否适合扩展Control以提供始终如一的安全Invoke/BeginInvoke功能?

如何解决《是否适合扩展Control以提供始终如一的安全Invoke/BeginInvoke功能?》经验,为你挑选了2个好方法。

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

/// 
/// Execute a method on the control's owning thread.
/// 
/// The control that is being updated.
/// The method that updates uiElement.
/// 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.
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,或者新的Winforms代码通常包含特定的专用方法以及相应的Invoke()相关管道,那么使用此方法更新一个新软件中的UI是一种很好的设计吗?所有这些UI更新?(我将首先尝试使用其他适当的后台处理技术,例如BackgroundWorker.)

有趣的是,这对ToolStripItems不起作用.我刚刚发现它们直接来自Component而不是Control.相反,ToolStrip应该使用包含的调用.

跟进评论:

一些评论表明:

if (uiElement.InvokeRequired)

应该:

if (uiElement.InvokeRequired && uiElement.IsHandleCreated)

请考虑以下msdn文档:

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

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

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

如果控件是在另一个线程上创建的,但尚未创建控件的句柄,则InvokeRequired返回false.这意味着如果InvokeRequired返回true,IsHandleCreated将始终为真.再次测试是多余和不正确的.



1> Samuel..:

您还应该创建Begin和End扩展方法.如果你使用泛型,你可以让电话看起来更好一些.

public static class ControlExtensions
{
  public static void InvokeEx(this T @this, Action 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(this T @this, Action action)
    where T : Control
  {
    return @this.BeginInvoke((Action)delegate { @this.InvokeEx(action); });
  }

  public static void EndInvokeEx(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);

关于Components,只需在表单或容器本身上调用即可.

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



2> Charlie Flow..:

我喜欢一般的想法,但我确实看到了一个问题.处理EndInvokes非常重要,否则可能会导致资源泄漏.我知道很多人不相信这一点,但确实如此.

这是一个谈论它的链接.还有其他人.

但我的主要反应是:是的,我认为你在这里有个好主意.


@LimitedAtonement:这实际上是"规则"的"例外",还是更公平地说规则要求调用`Delegate.BeginInvoke`来通过调用`Delegate.EndInvoke`来平衡,但是强加没有要求其他无关的函数也碰巧被命名为`BeginInvoke`?由于`Control.BeginInvoke`与`Delegate.BeginInvoke`没有任何关系,所以适用于前者的规则应该适用于后者.
FWIW,我记得在MSDN上看到,在这个单一的,特殊的,神奇的,特殊情况下,它被记录为不需要.但是我没有链接,直到我这样做才是真的.:)
来自Jon链接的相关部分:>>"我刚收到WinForms团队的官方消息.没有必要调用Control.EndInvoke.你可以以"一见不醒"的方式调用BeginInvoke而不受惩罚." <<注意在评论中,它是从2003年5月开始的,但它可能仍然是真的.
您不必调用Control.EndInvoke,因为Control.BeginInvoke调用解析为PostMessage到控件线程的消息循环.您无法轻易判断此消息是否得到处理,并且没有返回值.
推荐阅读
拾味湖
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有