2011-05-20 144 views
2

訪問一個WPF的FlowDocument在後臺在後臺進程中

我的問題涉及訪問UI對象,在WPF背景訪問一個WPF的FlowDocument。我看過幾十個示例應用程序,所有這些應用程序都很簡單,易於使用,其中95%告訴你如何顯示進度條。這不是我想要的......

我的問題是這樣的:我想通過訪問RichTextBox中的FlowDocument來執行長任務(或許多長任務)。確切的任務在這裏沒有關係,但是一個例子可能是掃描整個文檔,並計算特定單詞出現的次數,或者有多少紅色字符......。在長文檔中,這些可能是相當耗時的任務,並且如果在前臺完成,將會大大限制UI並使其無響應。我只想解析FlowDocument;我不想對它做任何更改。

所以這就是我想要做的事情。顯而易見的解決方案是在後臺進行,但問題是......怎麼做?我已經實現了似乎是一個答案,但它只是對我來說「不適合」,這就是爲什麼我在這裏尋求幫助。

我跟隨「解決方案」

我的「解決方案」使用它調用UI對象的調度,以確保正確的線程訪問一個BackgroundWorker ......和「出現」它做的工作。但是這樣做嗎?.............. 我已經大大簡化了我的「解決方案」,使它變得簡單(我希望)遵循我所做的...。

WithEvents worker As BackgroundWorker 
Private Delegate Sub DelegateSub() 
Private theDocument As FlowDocument 

''' <summary> 
''' Triggers the background task. Can call from anywhere in main code blocks 
''' </summary> 
Private Sub StartTheBackgroundTask() 

    worker = New BackgroundWorker 
    worker.RunWorkerAsync() 

End Sub 

''' <summary> 
''' In the background, hands the job over to the UI object's Dispatcher 
''' </summary> 
Private Sub HandleWorkerDoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles worker.DoWork 

    Dim priority As System.Windows.Threading.DispatcherPriority 
    Dim theLongRunningTask As DelegateSub 

    '(1) Define a delegate for the Dispatcher to work with 
    theLongRunningTask = New DelegateSub(AddressOf DoTheTimeConsumingTask) 

    '(2) Set Dispatcher priority as required 
    priority = System.Windows.Threading.DispatcherPriority.Background 

    '(3) Add the job to the FlowDocument's Dispatcher's tasks 
    theDocument.Dispatcher.BeginInvoke(theLongRunningTask, priority) 

End Sub 

''' <summary> 
''' Sub whose logic accesses, but does not change, the UI object 
''' </summary> 
Private Sub DoTheTimeConsumingTask() 

    'For example...... 

    For Each bl As Block In theDocument.Blocks 

     '......do something 

    Next 

End Sub 

雖然這似乎工作,因爲我看到它的問題是,除了觸發與BackgroundWorker的任務,幾乎所有的長期運行的任務由UI對象的調度處理。所以BackgroundWorker實際上並沒有做任何工作。這是我關心的部分;我看不到我如何得到什麼,如果分派器捆綁起來做所有的工作

選項2

因此,它似乎更符合邏輯,我認爲我會更好「這扭曲「並將Dispatcher的委託設置爲指向實例化並啓動BackGroundWorker的子(我認爲Dispatcher線程將擁有BackgroundWorker的線程),並執行BackgroundWorker的DoWork事件中的所有工作。這種「感覺」是正確的...。

所以我想這一點,而不是:

WithEvents worker As BackgroundWorker 
Private Delegate Sub DelegateSub() 
Private theDocument As FlowDocument 

''' <summary> 
''' Triggers the background task. Can call from anywhere in main code blocks 
''' </summary> 
Private Sub StartTheBackgroundTask() 

    Dim priority As System.Windows.Threading.DispatcherPriority 
    Dim theTask As DelegateSub 

    '(1) Define a delegate for the Dispatcher to work with 
    theTask = New DelegateSub(AddressOf RunWorker) 

    '(2) Set Dispatcher priority as required 
    priority = System.Windows.Threading.DispatcherPriority.Normal 

    '(3) Add the job to the Dispatcher's tasks 
    theDocument.Dispatcher.BeginInvoke(theTask, priority) 

End Sub 

''' <summary> 
''' Creates and starts a new BackGroundWorker object 
''' </summary> 
Private Sub RunWorker() 

    Worker = New BackgroundWorker 
    Worker.RunWorkerAsync() 

End Sub 

''' <summary> 
''' Does the long task in the DoWork event 
''' </summary> 
Private Sub HandleWorkerDoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles worker.DoWork 

    DoTheTimeConsumingTask() 

End Sub 

''' <summary> 
''' Sub whose logic accesses, but does not change, the UI object 
''' </summary> 
Private Sub DoTheTimeConsumingTask() 

    'For example...... 

    For Each bl As Block In theDocument.Blocks 

     '......do something 

    Next 

End Sub 

對我來說,所有的似乎更符合邏輯。我推測Dispatcher將擁有BackgroundWorker,而BackgroundWorker將繼續完成所有的長時間工作,並且所有事情都將在UI線程上進行。那麼... ...對於邏輯思考來說太多了......(通常對於WPF來說是致命的!)......它不會。它與通常的「不同線程」錯誤崩潰。所以,第二個想法似乎是一個更加優雅的解決方案,原來是一個失敗者!

我的問題則是:

  1. 是我的「解決方案」的解決方案,或不?
  2. 我哪裏錯了?
  3. 怎樣才能改善「解決方案」,使調度員不被長時間的工作束縛......這正是我試圖避免的情況?

還有一個問題。請注意,我必須使用FlowDocument的調度程序來完成這項工作。如果我使用System.Windows.Threading.Dispatcher.CurrentDispatcher來代替,那麼委託子(DoTheTimeConsumingTask)不會被調用 - 所有意圖和目的都不會發生。有人可以解釋爲什麼不,請嗎?

我還沒有來找你作爲第一停靠港。我已經嘗試了幾十種選擇,並且還沒有發現任何感覺完全正確的東西(除了我的第二個選項,它不起作用),所以我請求一些指導。

回答

3

您面臨的主要問題是FlowDocument派生自DispatcherObject,因此您必須使用其Dispatcher來訪問它。你試圖用這個東西做的所有事情都會採取將項目放在Dispatcher的工作隊列中並等待它執行它們的形式。其中,如果Dispatcher是處理用戶界面的那個,則會導致您正在嘗試避免的內容:Dispatcher正在執行您的工作項目時,剩下的所有鼠標單擊和按鍵都堆積在Dispatcher'工作隊列,用戶界面將是不負責任的。

你從FlowDocument得到的內容是DispatcherObject,它的內容在長時間運行的任務處理時不能改變。在任務完成後,隊列中的鼠標點擊和擊鍵可能會改變它,但在運行時,它們只會累積。這實際上很重要;如果您的能夠使用Dispatcher避開,您將面臨在任務運行時用戶界面中的某些內容更改了FlowDocument的情況。那麼你會有什麼通常被稱爲「問題」。

即使您可以克隆FlowDocument並從UI的調度程序中斷開該克隆,它仍然是一個DispatcherObject,並且您仍然遇到同樣的問題,試圖同時執行多個任務;你可以選擇序列化你的訪問權限或看你的後臺線程崩潰。

要解決這個問題,你需要做的是製作一些非FlowDocument的非凍結快照。然後在快照上運行你的任務。這樣,如果UI正在運行,並且在任務運行時更改FlowDocument,則不會搞亂您的遊戲。

我該怎麼辦:使用XamlWriter並將FlowDocument序列化爲XDocument。序列化任務涉及Dispatcher,但一旦完成,您可以根據需要運行儘可能多的古怪並行數據分析,UI中的任何內容都不會影響它。 (一旦它是一個XDocument你用XPath查詢它,這是一個很好的錘子,只要你的問題實際上是釘子。)

+0

無法發表評論;抱歉 – SophieT 2011-05-23 16:08:24

+0

我試圖使用Async = True訪問FlowDocument,並且遇到了類似的問題。你從DispatchObject派生出來的解釋清楚了事情。 +1 – Paparazzi 2011-09-19 00:56:21