2010-08-14 77 views
4

我有一個相當簡單的線程問題。如何將信息從ThreadPool.QueueUserWorkItem傳遞迴UI線程?

我在寫一個簡單的實用程序,它將根據用戶定義的參數運行各種SQL腳本。

爲了保持UI的響應並提供關於正在執行的腳本的狀態的反饋,我決定使用ThreadPool.QueueUserWorkItem將適合於處理各種腳本的執行(通過SMO)。

不過,我有點困惑,我怎麼可以中繼SMO將返回到UI線程的輸出信息。

對於此實用程序,我使用WPF和MVVM進行演示。我想我會有一個ScriptWorker類,我可以傳遞參數和位置以及運行腳本的順序。

在我運行每個腳本後,我想以某種方式將結果返回到UI線程,以便它更新輸出窗口,然後我希望工作人員移動到下一個任務。

我確定這是一個基本問題,但在看到QueueUserWorkItem後,我發現我基本上是通過回調來開始工作的,但我不確定如何完成我想要完成的任務。

我立足我的假設關閉此Microsoft文章的:

http://msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx

感謝您的信息!

+3

我會看看BackgroundWorker類。它使事情變得輕鬆,可以輕鬆地運行後臺進程並將更新發送到UI線程 – SwDevMan81 2010-08-14 16:29:29

回答

8

QueueUserWorkItem在技術上會工作,但是是非常低級的。有更簡單的方法。

我推薦使用.NET 4.0的新功能Task。它完全符合你的要求,包括將結果或錯誤條件同步到另一個線程(本例中爲UI線程)。

如果.NET 4.0不是一個選項,那麼我會建議BackgroundWorker(如果你的後臺處理不太複雜),或者像Hans提到的異步代理。如果您使用異步代理,請使用AsyncOperation類將結果編組回到UI線程。

Task選項非常好,因爲它非常自然地處理父/子任務。 BackgroundWorker不能嵌套。另一個考慮是取消; TaskBackgroundWorker內置支持取消,但對於異步代表,你必須自己做。

TaskBackgroundWorker複雜一點的唯一地方是正在進行報告。這不像BackgroundWorker那麼容易,但我有一個包裝on my blog以儘量減少。

總之,按優先順序:

  1. Task - 支持錯誤的正確封送處理,因此,取消和父/子嵌套的概念。它的一個弱點是進度報告並不簡單(您必須創建另一個任務並將其安排到UI線程)。
  2. BackgroundWorker - 支持適當的錯誤編組,結果的概念,取消和進度報告。它的一個弱點是不支持父/子嵌套 ,這限制了其在API中的使用,例如業務層。
  3. Delegate.BeginInvokeAsyncOperation - 支持正確的erroshaling,結果的概念和進度報告。但是,沒有內置的取消概念(儘管可以使用volatile bool手動完成)。它也不支持父母/孩子的嵌套。
  4. Delegate.BeginInvokeSynchronizationContext - 除了直接使用SynchronizationContext之外,這與選項(3)相同。代碼稍微複雜一些,但是折衷是支持父/子嵌套。所有其他限制與選項(3)相同。
  5. ThreadPool.QueueUserWorkItemAsyncOperationSynchronizationContext - 支持進度報告的概念。取消遭遇與選項(3)相同的問題。編組錯誤並不容易(特別是保留堆棧跟蹤)。另外,如果使用SynchronizationContext而不是AsyncOperation,則只能使用父/子嵌套。此外,這個選項不支持結果的概念,所以任何返回值都需要作爲參數傳遞。

正如你所看到的,Task是明顯的贏家。應該使用它,除非.NET 4.0不是一個選項。

+0

謝謝你的詳細解答! – 2010-08-15 21:04:47

+0

@Stephen謝謝你的擴展解釋和故障。這對理解不同的方式非常有幫助。順便說一下,您的博客鏈接似乎已過時。 – 2017-04-05 23:22:23

-1

This article有一個簡單的例子,你想要什麼。

要回到UI線程,您需要參考ISynchronizeInvoke接口。例如Form類實現了這個接口。

僞代碼,你可以做這樣的事情:

public class MyForm : Form 
{ 
    private OutputControl outputControl; 

    public void btnClick(...) 
    { 
     // Start a long running process that gives feedback to UI. 
     var process = new LongRunningProcess(this, outputControl); 
     ThreadPool.QueueUserWorkItem(process.DoWork); 
    } 
} 

class LongRunningProcess 
{ 
    // Needs a reference to the interface that marshals calls back to the UI 
    // thread and some control that needs updating. 
    public LongRunningProcess(ISynchonizeInvoke invoker, 
           OutputControl outputControl) 
    { 
     this.invoker = invoker; 
     this.outputControl = outputControl; 
    } 

    public void DoWork(object state) 
    { 
     // Do long-running job and report progress. 
     invoker.Invoke(outputControl.Update(...)); 
    } 
} 

注意,在這個例子中,OutputControl是控制權,因此也實現了ISynchronizeInvoke接口,所以你也可以選擇直接致電Invoke這個控制。

上面描繪的方法是相當低級的,但會給您很多控制權,尤其是您想要報告進度的方式。 BackgroundWorker爲您提供更高級的解決方案,但控制更少。您只能通過無類型UserState屬性提供進度狀態。

+2

-1:過時的'ISynchronizeInvoke'接口未被轉入WPF,並且在該UI框架中不受支持。 – 2010-08-14 17:40:53