2017-04-04 37 views
0

我创建了一个使用插件的应用程序。插件包含我想添加到主ToolStrip容器面板(在Form1类中)的ToolStrip。这很容易container.TopToolStripPanel.Controls.Add(plugin.PluginToolStrip;,但是如果我想在单独的线程中运行插件代码,那么它并不容易。 (我使用多线程简单的方式来卸载插件,我只需要杀死插件线程,并从主窗体中删除ToolStrip)如何将Control从另一个线程添加到Control,而无需调用?

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

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

我decompilled 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

简短答案是否定的,从一个线程更新一个UI,而不使用像SafeInvoke这样的安全交叉线程机制是* no no *,并且会产生令人不快的故障并且很难找到错误。原则和规则不仅适用于C#/ Windows,它适用于Java,Android,iOS等。您应该考虑重新构建插件机制,以便能够以有利于UI平滑的方式使用跨线程调用。 – t0mm13b

回答

0

我禁用CheckForIllegalCrossThreadCalls = FALSE;允许未使用调用无效

这解决不了任何问题的性质只是能够抛出异常的时候。你的代码做了错误的事情,但是禁用它并不能解决那些例外在那里试图帮助你避免的基本问题。

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

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

杀死线程本身是危险的。不能保证您可以安全地杀死一个线程,而不会影响您的进程的其余部分或损坏其数据。即使在大多数情况下,您也可能会忽略它,但这不是管理事物的可靠方法。

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

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

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

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

这实际上是一个可行的事情。很可能,因为你在处理工具栏,所以插件的用户交互仅限于几个简单的控件(比如按钮,菜单等),你也许可以设计一个体面的API来允许必要的跨域通讯。但是,您必须真的想要这个级别的安全性,而不是冒着用户可能使用可能会减少整个过程的错误插件的风险。这可以做到并不意味着这将是值得的努力。

相关问题