如何在没有Invoke的情况下从另一个线程添加Control to Control?

时间:2017-04-04 19:27:41

标签: c# multithreading exception plugins toolstrip

我创建了一个使用插件的应用。插件包含ToolStrip,我想将其添加到主ToolStrip容器面板(在Form1类中)。这很容易container.TopToolStripPanel.Controls.Add(plugin.PluginToolStrip;,但如果我想在分离的线程中运行插件代码,那么它就不那么容易了。 (我使用多线程方便卸载插件,我只需要杀死插件线程,并从主窗体中删除ToolStrip)

我禁用CheckForIllegalCrossThreadCalls = false;以允许不使用Invoke void。但是当我想从另一个线程运行container.TopToolStripPanel.Controls.Add(plugin.PluginToolStrip);时,程序会抛出ArgumentException并说我无法执行此操作。

那么,我如何创建可能会杀死插件线程的插件架构? (我想给用户提供简单管理插件的可能性)

我解压缩了System.Windows.Forms.dll以查看它抛出异常的位置,我看到了:

            /// <summary>Adds the specified control to the control collection.</summary>
            /// <param name="value">The <see cref="T:System.Windows.Forms.Control" /> to add to the control collection. </param>
            /// <exception cref="T:System.Exception">The specified control is a top-level control, or a circular control reference would result if this control were added to the control collection. </exception>
            /// <exception cref="T:System.ArgumentException">The object assigned to the <paramref name="value" /> parameter is not a <see cref="T:System.Windows.Forms.Control" />. </exception>
            public virtual void Add(Control value)
            {
                if (value == null)
                {
                    return;
                }
                if (value.GetTopLevel())
                {
                    throw new ArgumentException(SR.GetString("TopLevelControlAdd"));
                }
                if (this.owner.CreateThreadId != value.CreateThreadId)
                {
                    throw new ArgumentException(SR.GetString("AddDifferentThreads")); //here!
                }
                /* [...] */
            }

然后我想如果我可以改变this.owner.CreateThreadId,那么我将能够传递这个(if (this.owner.CreateThreadId != value.CreateThreadId)),并且程序不会抛出异常。在第6315行,我看到了这段代码:

internal int CreateThreadId
        {
            get
            {
                if (this.IsHandleCreated)
                {
                    int num;
                    return SafeNativeMethods.GetWindowThreadProcessId(new HandleRef(this, this.Handle), out num);
                }
                return SafeNativeMethods.GetCurrentThreadId();
            }
        }

我们只有内心:(

我该怎么办?你有什么建议吗?谢谢,抱歉我的英语不好......

1 个答案:

答案 0 :(得分:0)

  

我禁用了CheckForIllegalCrossThreadCalls = false;允许不使用Invoke void。

没有解决任何问题。该属性只允许在代码执行错误时抛出异常,但禁用它并不能解决异常所在的基础问题,以帮助您避免。

更大的问题是UI对象具有“线程亲和力”。它们由特定线程拥有,即创建其窗口句柄的线程,如果您尝试从任何其他线程访问这些对象,则该访问可能会失败或导致控件无法正常运行。

  

那么,我如何创建可能会杀死插件线程的插件架构? (我想给用户提供简单管理插件的可能性)

杀死线程本身就很危险。无法保证您可以安全地终止某个线程,其方式不会占用您的其余进程或损坏其数据。即使在大多数情况下,你也可以逃脱它,但这不是一种可靠的管理方式。

理论上,如果你决定继续沿着这条路走下去,一个选择是继续在新线程中创建插件控件。然后你必须确保该线程是一个STA线程,你必须通过调用该线程中的Application.Run()为该线程提供一个消息循环。

但是,您仍然会遇到插件控件将托管在由其他线程拥有的窗口中的问题。这是另一个危险区域,假设您可以完全正常工作,可能很难正常工作。拥有一个由一个线程拥有的窗口,并且它是一个由不同线程拥有的窗口的子窗口将有自己的陷阱。

我知道能够安全终止插件代码的最可靠方法是在自己的AppDomain中运行该代码。然后你可以随意拆掉AppDomain。由于域无法直接访问彼此的数据,因此可以避免通常会中断线程的问题。

但是,该解决方案需要在域之间进行某种代理。您将无法将对象的实际用户界面部分置于单独的域中。相反,您必须设置一个系统,用户通过该系统与您的代码所控制的某个组件进行交互,其中这些交互转换为与插件实现的某些代理通信。

这实际上是一件可行的事情。由于您正在处理工具栏,因此插件的用户交互仅限于几个简单的控件(如按钮,菜单等),您可能可以设计一个合适的API以允许必要的跨域通讯。但是你必须真正想要这种安全级别,而不是仅仅承担用户可能使用可能会占用整个过程的错误插件的风险。它可以做到并不意味着它值得付出努力。