簡體   English   中英

如何在多個線程之間進行通信?

[英]How do I communicate between multiple threads?

我正在為另一個程序編寫一個插件,該程序使用本機程序打開一系列文件以從中提取一些數據。 我遇到的一個問題是該過程需要很長時間,我想保持用戶界面不掛。 另外,我還想讓用戶在完成之前取消該過程。 在過去,我使用后台工作者來處理這類事情,但在這種情況下,我認為BackgroundWorker不會起作用。

要通過API創建插件,我使用的可以通過繼承IAPICommand接口來創建自定義命令。 該接口包括Execute(應用程序app)方法。 然后實例化該類,當用戶在程序中喚起自定義命令時,程序將調用Execute()方法。

Execute()方法在調用時傳遞對當前Application對象的引用,並且該應用程序對象用於打開文件以從中提取數據。 但是,應用程序實例無法在原始Execute()線程之外的線程請求時打開文檔。

因此,通常UI將存在於主線程上,並且將在輔助線程上執行耗時的數據提取。 但是,在這種情況下,必須在主線程上執行數據提取,並且我需要為UI創建輔助線程。

這是代碼的精簡版本。

class MyCommand:IAPICommand
{
    public void Execute(Application app) // method from IAPICommand
    {
        Thread threadTwo= new Thread(ShowFormMethod);
        threadTwo.Start();
    }

    public void ProcessWidget(Widget w, Application app)
    { 
        //uses an App to work some magic on C
        //app must be called from the original thread that called ExecuteCommand()
    }

    //method to open custom form on a seperatethread
    public void ShowFormMethod()
    {
      MyForm form = new MyForm();
      form.ShowDialog();  
    }
}

這是一個流程圖,顯示了我認為這應該最終起作用的方式。

alt text http://dl.dropbox.com/u/113068/SOMLibThreadingDiagram.jpg

  1. 這個圖表是否有意義,如果是這樣,我甚至采取正確的方法來解決這個問題?
  2. 一旦主線程啟動UI線程,我希望它等待用戶選擇要處理的小部件,或者通過關閉表單結束命令(圖中的紅色數字)。 如何使主線程等待,以及如何觸發它繼續處理或在UI線程結束時繼續結束? 我以為我可以讓主線程在Monitor鎖上等待。 然后,UI線程將填充要處理的靜態Widgets列表,然后脈沖主​​線程以觸發處理。 當窗體關閉時,UI線程也會激活主線程,如果要處理的窗口小部件列表為空,主線程將知道繼續到命令的結尾。
  3. 如何允許主線程將窗口小部件處理的進度或完成傳回UI線程(圖中的黃色箭頭)? 我只是使用Form的BeginInvoke()方法來執行此操作嗎?
  4. 如何允許UI線程取消窗口小部件處理(圖中的綠色箭頭)? 我想我可以設置一個靜態布爾標志,在處理每個小部件之前檢查它?

在應用程序中創建多個創建表單的線程通常是個壞主意。 使這項工作並非不可能,但它比你想象的要困難得多,因為父子關系中的表單會相互發送消息,而當它們發送消息時,發送消息的表單會阻塞接收處理它。

將此信息與您明確執行的線程之間的消息傳遞或同步相結合,並且很容易導致死鎖。 因此,一般情況下,最好確保為用戶界面保留主線程,並在沒有UI的其他線程中執行所有處理。

如果符合該設計,則后台線程可以使用Control.BeginInvoke將消息傳遞給UI線程,而無需等待消息處理。

除了其他答案,我建議您使用ProcessWidget中的回調方法將進度傳遞回調用線程。 要過早地停止工作線程,如果它經常更新調用者,您可以使用回調將停止信號返回到工作線程。 或者使用單獨的回調方法定期檢查go / no-go。 或者設置一個(gasp!)全局靜態標志,工作者定期檢查。 或者在工作線程上調用Thread.Abort並讓它捕獲ThreadAbortException以清理任何資源。

我假設主機應用程序是一個WinForms應用程序。

您需要從Execute方法中的原始線程保存SynchronizationContext ,然后調用其Send方法在主機的UI線程上執行代碼。

例如:

class MyCommand:IAPICommand
{
    SynchronzationContext hostContext;
    public void Execute(Application app) // method from IAPICommand
    {
        hostContext = SynchronzationContext.Current;
        Thread threadTwo = new Thread(ShowFormMethod);
        threadTwo.Start();
    }

    public void ProcessWidget(Widget w, Application app)
    { 
        //uses an App to work some magic on C
        //app must be called from the original thread that called ExecuteCommand()
        SomeType someData = null;
        hostContext.Send(delegate { someData = app.SomeMethod(); }, null);
    }
}

如果你看一下Java swing,它就是一個很好的例子:

1)主線程負責處理所有UI請求。 這將從應用程序中刪除任何競爭條件。

2)任何時候要完成任何“工作”,產生一個線程(或一個線程池)並完成工作。 因此,除了幾微秒​​之外,主線程不會被阻止,並且無論發生什么,UI都是完全響應的。

3)在所有語言中都必須有線程中斷機制。 在java中,您在線程上調用.interrupt(),並且當前運行的線程在執行任何地方時都會拋出InterruptedException。 你的工作是抓住那個異常,弄清楚你是否真的被打斷了(讀這個部分的javadocs),如果你只是讓自己死(退出run方法)。

1 + 2 =不引人注目的客戶互動

3 =殺死線程

3的替代(如果3太復雜)是給線程一個方法.kill(); 該方法設置一個kill標志。 當您從循環中的硬盤驅動器中讀取緩沖區時,請檢查是否設置了kill標志,是否已經脫離循環,關閉處理程序,並返回run方法。

編輯:抱歉忘了提及進度報告:

您的線程應該具有公開的線程安全方法來獲取“進度報告”,或者更確切地說是包含有關進度信息的數據結構。 您的UI線程應定期(比如說每0.5秒)檢查線程的進度報告並更新UI的進度條。 通過UI線程檢查,我的意思是顯示進度的窗口小部件請求使用計時器的最新信息重新呈現,直到完成。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM