簡體   English   中英

將文本添加到RichTextBox Async #C / WPF

[英]Adding text to RichTextBox Async #C / WPF

我想要實現的是在每次操作后將文本添加到RichTextBox。 問題是,這些操作需要一些時間,而不是在每個操作完成后查看附加文本,我會在例程結束時查看它們。

半偽代碼:

RichTextBox richTextBox = new RichTextBox()

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

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

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

問題是我在操作3完成后查看操作1和2的附加文本。

我在某處讀到了我需要使用的東西叫做BackgroundWorker ???

使用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在一個后台線程中運行所有任務,並定期報告進度。

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

如果UI忙於做其他事情,UI就無法繪制內容。 這是怎么回事:

  • 您單擊一個按鈕
  • UI線程獲取按鈕單擊消息,並調用您的單擊處理函數
    • 現在,在單擊處理函數完成之前,UI無法重繪或執行任何其他更新。
  • 您的Operation1函數完成,並附加到RichTextBox
    • 用戶界面無法更新,因為它仍然無法運行您的代碼
  • 您的Operation2函數完成,並附加到RichTextBox
    • 用戶界面無法更新,因為它仍然無法運行您的代碼
  • 您的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方法。
  • 我們使用magic await關鍵字等待線程池完成執行operation1。 這背后的事情在道德上與此相當:
    • 暫停當前​​函數 - 從而釋放UI線程以重新繪制自身
    • 當我們等待的東西完成時恢復

因為我們現在在線程池中運行長操作,所以UI線程可以在需要時繪制它的更新,並且您將看到消息按照您的預期添加。

但是,這有一些潛在的缺點:

  1. 因為您的Operation1方法未在UI線程上運行,如果它需要訪問任何UI相關數據(例如,如果它想要從文本框中讀取某些文本等),則它不能再執行此操作。 您必須首先執行所有UI內容,並將其作為參數傳遞給Operation1方法
  2. 將花費很長時間(超過100毫秒)的東西放入線程池通常不是一個好主意,因為線程池可以用於其他事情(如網絡操作等)並且通常需要有一些空閑容量為了這。 如果你的應用程序只是一個簡單的GUI應用程序,這不太可能影響你。
    如果它是一個問題,你可以使用await Task.Factory.StartNew<bool>(_ => Operation1(), null, TaskCreationOptions.LongRunning)))而且每個任務都將在它自己的線程中運行而不是使用線程池了。 雖然有點丑陋:-)

暫無
暫無

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

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