【问题标题】:How to add Control to Control from another thread, without Invoke?如何在不调用的情况下从另一个线程将控件添加到控件?
【发布时间】:2017-04-04 19:27:41
【问题描述】:

我创建了一个使用插件的应用程序。插件包含我想添加到主 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();
            }
        }

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

我能做什么?你有什么建议吗?感谢和抱歉我的英语不好......

【问题讨论】:

  • 简短的回答是否定的,从线程更新 UI,而不使用安全的跨线程机制,例如 SafeInvokeno no 并且会产生令人不快的故障和困难寻找错误。原则和规则不仅适用于 C#/Windows,也适用于 Java、Android、iOS 等。您应该考虑重新架构插件机制,以便能够以有利于 UI 流畅性的方式使用跨线程调用。跨度>

标签: c# multithreading exception plugins toolstrip


【解决方案1】:

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

这解决不了任何问题。该属性仅在您的代码出现错误时启用引发异常,但禁用它并不能解决异常试图帮助您避免的潜在问题。

更大的问题是 UI 对象具有“线程关联性”。它们属于特定线程,即创建它们的窗口句柄的线程,如果您尝试从任何其他线程访问这些对象,则该访问可能会失败或导致控件操作不正确。

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

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

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

但是,您仍然会遇到插件控件将托管在由不同线程拥有的窗口中的问题。这是另一个危险领域,如果您完全可以做到,可能很难正常工作。拥有一个由一个线程拥有的窗口,而它又是由另一个线程拥有的一个窗口的子窗口,这将有其自身的缺陷。

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-16
    • 1970-01-01
    • 1970-01-01
    • 2015-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多