扩展 Control 以提供始终如一的安全 Invoke/BeginInvoke 功能是否合适? [英] Is it appropriate to extend Control to provide consistently safe Invoke/BeginInvoke functionality?

查看:17
本文介绍了扩展 Control 以提供始终如一的安全 Invoke/BeginInvoke 功能是否合适?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In the course of my maintenance for an older application that badly violated the cross-thread update rules in winforms, I created the following extension method as a way to quickly fix illegal calls when I've discovered them:

/// <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 需要为真:

I like how I can leverage closures to read, also, though forceSynchronous needs to be true in that case:

string taskName = string.Empty;
this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true);

我不怀疑这种方法对于修复遗留代码中的非法调用的有用性,但是新代码呢?

I don't question the usefulness of this method for fixing up illegal calls in legacy code, but what about new code?

当您可能不知道哪个线程正在尝试更新用户界面时,使用这种方法来更新新软件中的用户界面是否是好的设计,或者新的 Winforms 代码通常是否应该包含具有适当的特定的专用方法Invoke() 与所有此类 UI 更新相关的管道?(当然,我会先尝试使用其他合适的后台处理技术,例如 BackgroundWorker.)

Is it good design to use this method to update UI in a piece of new software when you may not know what thread is attempting to update the ui, or should new Winforms code generally contain a specific, dedicated method with the appropriate Invoke()-related plumbing for all such UI updates? (I'll try to use the other appropriate background processing techniques first, of course, e.g. BackgroundWorker.)

有趣的是,这不适用于 ToolStripItems.我最近才发现它们直接来自 Component来自 Control.相反,应该使用包含 ToolStrip 的调用.

Interestingly this won't work for ToolStripItems. I just recently discovered that they derive directly from Component instead of from Control. Instead, the containing ToolStrip's invoke should be used.

一些评论表明:

if (uiElement.InvokeRequired)

应该是:

if (uiElement.InvokeRequired && uiElement.IsHandleCreated)

考虑以下 msdn 文档:

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

This means that InvokeRequired can return false if Invoke is not required (the call occurs on the same thread), or if the control was created on a different thread but the control's handle has not yet been created.

如果控件的句柄尚未创建,您应该不是简单地调用属性、方法,或控件上的事件.这有可能导致控件的句柄为在后台线程上创建,隔离线程上的控制没有消息泵并制作应用不稳定.

In the case where the control's handle has not yet been created, you should not simply call properties, methods, or events on the control. This might cause the control's handle to be created on the background thread, isolating the control on a thread without a message pump and making the application unstable.

您可以通过以下方式防范这种情况还检查的值IsHandleCreated 当 InvokeRequired在后台线程上返回 false.

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

If the control was created on a different thread but the control's handle has not yet been created, InvokeRequired returns false. This means that if InvokeRequired returns true, IsHandleCreated will always be true. Testing it again is redundant and incorrect.

推荐答案

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

I like the general idea, but I do see one problem. It is important to process EndInvokes, or you can have resource leaks. I know a lot of people don't believe this, but it really is true.

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

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

But the main response I have is: Yes, I think you've got a nice idea here.

这篇关于扩展 Control 以提供始终如一的安全 Invoke/BeginInvoke 功能是否合适?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆