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

How to add Control to Control from another thread, without Invoke?

我创建了一个使用插件的应用程序。插件包含 ToolStrip,我想将其添加到主 ToolStrip 容器面板(在 Form1 class 中)。这很容易 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,而且是内部的:(

我能做什么?你有什么建议吗?谢谢,对不起我的英语不好...

I disabled CheckForIllegalCrossThreadCalls = false; to allow not-using Invoke void.

这解决不了任何问题。 属性 仅在您的代码出错时启用抛出异常,但禁用它并不能解决异常存在的潜在问题,以帮助您避免。

更大的问题是 UI 个对象有 "thread affinity"。它们由特定线程拥有,即创建其 window 句柄的线程,如果您尝试从任何其他线程访问这些对象,该访问可能会失败或导致控件运行不正确。

So, how can I create plugins architecture with possibility to kill plugins threads? (I want to give user possibility of easy way of management plugins)

终止线程本身就是危险的。无法保证您可以安全地终止一个线程,这种方式不会破坏您的进程的其余部分或破坏其数据。即使在大多数情况下,您也可能会侥幸逃脱,但这不是管理事物的可靠方式。

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

但是,您仍然会 运行 遇到这样的问题,即您的插件控件将托管在另一个线程拥有的 window 中。这是另一个危险的领域,可能很难正确地工作,假设你能做到。拥有一个线程拥有的 window,并且它是另一个线程拥有的 window 的子级将有其自身的缺陷。

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

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

这实际上是一件可行的事情。由于您正在处理工具栏,因此插件的用户交互很可能仅限于几个简单的控件(如按钮、菜单等),您可以设计一个像样的 API 来允许必要的跨域通信。但是您必须 真的 想要这种安全级别,而不是冒着用户可能会使用可能导致整个过程中断的错误插件的风险。可以完成并不意味着值得付出努力。