繁体   English   中英

如何在不调用的情况下从另一个线程向控件添加控件?

[英]How to add Control to Control from another thread, without Invoke?

我创建了一个使用插件的应用程序。 插件包含要添加到主ToolStrip容器面板(在Form1类中)的ToolStrip。 这是简单的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( 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();
            }
        }

我们只有get,它是内部的:(

我能做什么? 你有什么建议吗? 谢谢,抱歉我的英语不好。

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

那什么也解决不了。 该属性仅在代码执行某些错误操作时才启用引发异常,但是禁用该属性并不能解决其中存在异常以试图帮助您避免的潜在问题。

更大的问题是UI对象具有“线程相似性”。 它们由特定线程(即在其内创建其窗口句柄的线程)所有,并且,如果您尝试从任何其他线程访问那些对象,则该访问可能会失败或导致控件无法正常运行。

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

杀死线程本身就是危险的。 无法保证您可以安全地杀死线程,而这种方式不会破坏进程的其余部分或破坏其数据。 您可能甚至在大多数时间都无法使用它,但这不是管理事物的可靠方法。

从理论上讲,如果决定继续走这条道路,其中一个方案是继续前进,在一个新的线程创建插件控制。 然后,您必须确保该线程是STA线程,并且必须通过在该线程中调用Application.Run()为该线程提供消息循环。

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

我知道能够安全地终止插件代码的最可靠方法是在自己的AppDomain运行该代码。 然后,您可以随意拆除AppDomain 由于域不能直接访问彼此的数据,因此可以避免正常情况下中止线程的问题。

但是,该解决方案将需要域之间的某种代理。 您将无法使对象的实际用户界面部分位于单独的域中。 取而代之的是,您必须建立一个系统,用户可以通过该系统与您的代码控制的某些组件进行交互,这些交互将转化为与插件实现之间的代理通信。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM