2017-04-04 69 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來允許必要的跨域通訊。但是,您必須真的想要這個級別的安全性,而不是冒着用戶可能使用可能會減少整個過程的錯誤插件的風險。這可以做到並不意味着這將是值得的努力。

相關問題