2010-08-13 60 views
2

短缺問題採用這種多線程問題的方法是什麼?

我想產生一個單一的後臺線程會處理(有一個線程像一個線程池)提交到隊列中的工作項目。一些工作項目能夠報告進展情況,有些則不能。我應該使用.NET的無數多線程方法中的哪一種?


龍解釋(避免詢問有關half which doesn't make any sense):

我的WinForms應用程序的主窗口被垂直分割爲兩半。左半部分包含帶有項目的樹視圖。當用戶在樹視圖中雙擊一個項目時,該項目在右半部分打開。幾乎所有的對象都有很多屬性,分成幾個部分(由製表符表示)。這些屬性的加載需要相當長的時間,通常大約10s,有時更多。每隔一段時間就會添加更多的屬性,所以時間會增加。

目前,我的單線程設計使UI在這個時候無法響應。自然這是不可取的。我想在背景中逐個加載東西,並且只要一個部件被加載就可以使用。對於其他部分,我會顯示一個帶有加載動畫或其他東西的佔位符選項卡。另外,雖然有些零件是在單一冗長的單片操作中加載的,但其他零件由許多較小的函數調用和計算組成,因此可能會顯示加載進度。對於這些部件,看到進展情況會很好(特別是如果他們掛在某個地方,會發生什麼情況)。

請注意,數據源不是線程安全的,所以我無法同時加載兩個部分。

哪種方法最適合實現此行爲?有沒有一些.NET類可以解除我的肩膀上的一些工作,或者我應該沮喪地用Thread

A ThreadPool沒有工作項目隊列管理,但沒有進度報告設施。另一方面,BackgroundWorker支持進度報告,但它是針對單個工作項目的。有沒有可能是兩者的結合?

回答

1

聽起來很棘手!

你說你的數據源不是線程安全的。那麼,這對用戶意味着什麼。如果他們在整個地方點擊,但不要等到屬性加載之後再點擊其他地方,他們可以點擊10個需要很長時間加載的節點,然後等待第10個節點。由於數據源訪問不是線程安全的,因此負載必須一個接一個地運行。這表明一個ThreadPool不是一個好的選擇,因爲它可以並行運行並破壞線程安全。如果一個負載可能會中途中止,以防止用戶在他們想要查看的頁面開始加載之前不得不等待最後9個節點加載,那將是一件好事。

如果加載可以中止,我建議一個BackgroundWorker將是最好的。如果用戶切換節點,並且BackgroundWorker已經處於忙碌狀態,請設置一個事件或其他信號來指示它應當中止現有工作,然後排隊新工作以加載當前頁面。

此外,請考慮,使線程池中的線程運行報告進度並不太棘手。要做到這一點通過進度對象的類型是這樣的QueueUserWorkItem電話:

class Progress 
{ 
    object _lock = new Object(); 
    int _current; 
    bool _abort; 

    public int Current 
    { 
    get { lock(_lock) { return _current; } } 
    set { lock(_lock) { _current = value; } } 
    } 

    public bool Abort 
    { 
    get { lock(_lock) { return _abort; } } 
    set { lock(_lock) { _abort = value; } } 
    } 
} 

線程可以寫入這一點,並在UI線程可以輪詢(從System.Windows.Forms.Timer事件)閱讀進度並更新進度條或動畫。

此外,如果您包含Abort屬性。如果用戶更改節點,用戶可以設置它。加載方法可以在整個操作過程中的各個位置檢查中止值,如果已設置,則返回而不完成加載。

說實話,你選擇的並不是很重要。所有三個選項都是在後臺線程上完成的。如果我是你,我會開始使用BackgroundWorker,因爲它很容易設置,如果你決定需要更多的東西,可以考慮切換到ThreadPool或純線程。

BackgroundWorker還具有以下優點:您可以使用它的完成事件(在主UI線程上執行該事件)來使用加載的數據更新UI。

+0

是的,如果用戶點擊10個節點,他會等待。實際上,用戶界面允許他打開幾個項目並同時查看它們,這是一個有效的場景。但我同意,如果他*關閉了其中一項,則加載應該中止。爲他目前正在觀看的項目設置優先級也是一個很好的功能,沒有想到這一點。所以是的,它變得混亂。我想那裏不會有任何遠程預製的東西。順便說一句 - 你不覺得一個簡單的'volatile int _current'應該是足夠的,如果只有一個線程正在寫入,並且只有一個線程正在讀取它? – 2010-08-13 09:46:06

+0

葉,揮發性應該沒問題。 – 2010-08-13 09:50:51

1

使用一個線程,在一個線程安全的集合放下你的工作,並使用調用當你更新你的UI做的正確的線程

2

.NET 4.0帶來了很多的改進,通過引入Task類型多線程,它表示一個可能的異步操作。

對於您的情況,我建議將每個屬性(或屬性組)的加載分解爲單獨的任務。任務包括「父」的概念,因此每個對象的加載可以是擁有屬性加載任務的父任務。

要處理取消操作,請使用新的統一取消框架。爲每個對象創建一個CancellationTokenSource,並將其CancellationToken傳遞給父任務(將其傳遞給其每個子任務)。這允許一個對象被取消,它可以在當前加載屬性完成後生效(而不是等到整個對象完成)。

要處理併發(或更合適的,併發),請使用ParallelExtensionsExtras sample library中的OrderedTaskScheduler。每個Task只代表需要安排的工作單元,並且通過使用OrderedTaskScheduler,確保順序執行(在ThreadPool線程上)。

UI進度更新可以通過創建UI更新Task並將其調度到UI線程來完成。我有一個on my blog的例子,我把一些比較尷尬的方法換成ProgressReporter輔助類型。

Task類型的一個好處是,它以自然的方式傳播異常和取消;這些往往是設計一個系統來處理像你這樣的問題更困難的部分。

+0

有趣,將檢查出來。 – 2010-08-13 10:58:46