2013-12-16 149 views
1

我想要實現的是將每個操作之後的文本添加到RichTextBox。 問題是,這些操作需要一些時間,而不是在每個操作完成後查看附加文本,而是在例程結束時查看它們。將文本添加到RichTextBox異步#C/WPF

半僞碼:

RichTextBox richTextBox = new RichTextBox() 

if (Operation1()) 
{ 
    richTextBox.AppendText("Operation1 finished"); 

    if (Operation2()) 
    { 
     richTextBox.AppendText("Operation2 finished"); 

     if (Operation3()) 
     { 
     richTextBox.AppendText("Operation3 finished"); 
     } 
    } 
} 

的問題是,我認爲操作1 & 2的附加文本的操作3完成後。

我在某處閱讀我需要使用一些名爲BackgroundWorker的東西?

回答

2

WPF(和大多數其他UI框架的工作方式)的方式是有一個UI線程,它處理所有UI事件(如按鈕單擊)和UI繪圖。

如果用戶界面忙於其他事情,界面無法繪製。發生的事情是這樣的:

  • 你點擊一個按鈕
  • UI線程獲得一個點擊按鈕的消息,並調用你的點擊處理函數
    • 現在,UI無法重繪或執行任何其他更新直到你的點擊處理函數完成。
  • 你Operation1功能完成後,你追加到RichTextBox
    • 的UI不能更新,因爲它仍然停留運行代碼
  • 你操作2函數結束了,你追加到RichTextBox
    • 的UI不能更新,因爲它仍然停留運行代碼
  • 您的Operation3函數完成,並且您追加到RichTextBox
    • 您的函數完成,現在UI線程是空閒的,它可以最終處理更新並重繪本身。

這就是爲什麼你看到一個暫停,然後將所有更新3一起。

你需要做的是讓代碼花費很長時間在不同的線程上運行,這樣UI線程可以隨時在你喜歡的時候重繪和更新。這個示例程序爲我的作品 - 它需要.NET 4.5編譯和運行

using System.Threading.Tasks; 

... 

// note we need to declare the method async as well 
public async void Button1_Click(object sender, EventArgs args) 
{ 
    if (await Task.Run(new Func<bool>(Operation1))) 
    { 
     richTextBox.AppendText("Operation1 finished"); 

     if (await Task.Run(new Func<bool>(Operation2))) 
     { 
      richTextBox.AppendText("Operation2 finished"); 

      if (await Task.Run(new Func<bool>(Operation3))) 
      { 
      richTextBox.AppendText("Operation3 finished"); 
      } 
     } 
    } 
} 

這裏會發生什麼事是,我們使用C#神奇async功能,並且操作的順序是這樣的:

  • 你點擊一個按鈕
  • UI線程得到一個按鈕點擊消息,並調用你的點擊處理函數
  • 不是直接調用Operation1的,我們把它傳遞給Task.Run。該輔助函數將在線程池線程上運行您的Operation1方法。
  • 我們使用神奇的await關鍵字來等待線程池完成執行操作1。這裏做的事情的幕後是這東西相當於道德:
    • 暫停當前功能 - ,從而騰出UI線程重新繪製本身
    • 簡歷,當我們等待事情完成

因爲我們的線程池現在正在運行的長時間操作,UI線程可以借鑑它的更新時就是了,你會看到這些消息被添加爲你所期望的。

有這個雖然一些潛在的缺點:

  1. 因爲你Operation1方法不是在UI線程上運行,如果需要訪問任何UI相關的數據(例如,如果想讀一些來自文本框的文本等),它不能再這樣做。你必須首先完成所有UI的工作,並將其作爲參數傳遞給Operation1方法
  2. 將花費很長時間(超過100ms)的東西放入線程池通常不是一個好主意,因爲線程池可以用於其他事情(如網絡操作等),並且通常需要爲此提供一些空閒容量。如果你的應用程序只是一個簡單的GUI應用程序,但這不太可能影響你。
    如果這對您來說是個問題,您可以改爲使用await Task.Factory.StartNew<bool>(_ => Operation1(), null, TaskCreationOptions.LongRunning))),並且每個任務都將在其自己的線程中運行,而不再使用線程池。這有點醜陋,但:-)
+0

感謝您的回覆。我收到這個錯誤:'await'操作符只能在異步方法中使用。考慮使用「異步」修飾符標記此方法,並將其返回類型更改爲「任務」。我錯過了什麼? – Erez

+0

@Erez - 根據編譯器錯誤,您需要使用'async'修飾符標記該方法。我已經更新了我的答案,以表明你如何做到這一點。你不需要改變它的返回類型爲任務,雖然 –

+0

謝謝,它的工作。但是,如果操作需要獲取參數呢?嘗試將值傳遞給方法時,我得到「方法名稱預期」。 – Erez

2

使用BackgroundWorker的,你只想把後臺工作納入DoWork,並更新到RunWorkerCompleted

var bw1 = new BackgroundWorker(); 
var bw2 = new BackgroundWorker(); 
var bw3 = new BackgroundWorker(); 

bw1.DoWork += (sender, args) => args.Result = Operation1(); 
bw2.DoWork += (sender, args) => args.Result = Operation2(); 
bw3.DoWork += (sender, args) => args.Result = Operation2(); 

bw1.RunWorkerCompleted += (sender, args) => { 
    if ((bool)args.Result) 
    { 
     richTextBox.AppendText("Operation1 ended\n"); 
     bw2.RunWorkerAsync(); 
    } 
}; 
bw2.RunWorkerCompleted += (sender, args) => { 
    if ((bool)args.Result) 
    { 
     richTextBox.AppendText("Operation2 ended\n"); 
     bw3.RunWorkerAsync(); 
    } 
}; 
bw3.RunWorkerCompleted += (sender, args) => { 
    if ((bool)args.Result) 
    { 
     richTextBox.AppendText("Operation3 ended\n"); 
    } 
}; 

bw1.RunWorkerAsync(); 

你會發現,這個運行相抵觸的「幹」。你總是可以考慮抽象的任務使用類似的每一步:

var operations = new Func<bool>[] { Operation1, Operation2, Operation3, }; 
var workers = new BackgroundWorker[operations.Length]; 
for (int i = 0; i < operations.Length; i++) 
{ 
    int locali = i; // avoid modified closure 
    var bw = new BackgroundWorker(); 
    bw.DoWork += (sender, args) => args.Result = operations[locali](); 
    bw.RunWorkerCompleted += (sender, args) => 
    { 
     txt.Text = string.Format("Operation{0} ended\n", locali+1); 
     if (locali < operations.Length - 1) 
      workers[locali + 1].RunWorkerAsync(); 
    }; 
    workers[locali] = bw; 
} 
workers[0].RunWorkerAsync(); 

你可以做的3倍以上,或使用ReportProgress在一個後臺線程運行的所有任務,並定期報告進展情況。

+0

我無法設法使我的代碼工作,你在這裏解釋。當我按下一個WPF窗口(Button_Click事件)中的一個按鈕時,我的操作被觸發,操作的結構形式爲:if(operation1 == true)then if(operation2 == true)then if(operation3 = = true)... – Erez

+0

@Erez啊,那有點不同了。看到我上面的編輯。 – McGarnagle

+0

它的工作! 我也使用[this](http://stackoverflow.com/questions/9732709/the-calling-thread-cannot-access-this-object-because-a-different-thread-owns-it)來調用文本附加。感謝您的時間! – Erez