一尘不染

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

c#

在维护严重违反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。)

有趣的是,这不适用于ToolStripItems。我最近才发现它们直接来自Component而不是Control。而是ToolStrip应使用contains的invoke。

后续评论:

一些评论建议:

if (uiElement.InvokeRequired)

应该:

if (uiElement.InvokeRequired && uiElement.IsHandleCreated)

考虑以下msdn文档

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

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

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

如果控件是在其他线程上创建的,但尚未创建控件的句柄,则InvokeRequired返回false。这意味着,如果InvokeRequiredreturn
trueIsHandleCreated将始终为true。再次测试是多余且不正确的。


阅读 200

收藏
2020-05-19

共1个答案

一尘不染

我喜欢总体思路,但确实看到了一个问题。处理EndInvokes很重要,否则可能会导致资源泄漏。我知道很多人不相信这一点,但这确实是事实。

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

但是我的主要答复是:是的,我认为您在这里有了一个不错的主意。

2020-05-19