简体   繁体   中英

Adding text to RichTextBox Async #C / WPF

What I am trying to achieve is to add text after every operation to a RichTextBox. The problem is, that these operations take some time and instead of viewing the appended text after every operation finishes, I view them all at the end of the routine.

Semi-Pseudo code:

RichTextBox richTextBox = new RichTextBox()

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

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

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

The problem is that I view the appended text of operation 1 & 2 after the operation 3 is finished.

I read somewhere that I need to use something called BackgroundWorker???

Using BackgroundWorker, you would just put the background work into DoWork , and the update into 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();

You'll notice that this runs afoul of "DRY". You could always consider abstracting the tasks for each step using something like:

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();

You could do the above 3 times, or use ReportProgress to run all tasks in one background thread, and periodically report progress.

The way that WPF (and most other UI frameworks work) is that there is a UI thread, which handles all the UI events (such as button clicking) and UI drawing.

The UI can't draw things if it's busy doing other things. What's happening is this:

  • You click a button
  • The UI thread gets a button click message, and invokes your click handler function
    • Now, the UI can't redraw or perform any other updates until your click handler function finishes.
  • Your Operation1 function finishes, and you append to the RichTextBox
    • The UI can't update because it's still stuck running your code
  • Your Operation2 function finishes, and you append to the RichTextBox
    • The UI can't update because it's still stuck running your code
  • Your Operation3 function finishes, and you append to the RichTextBox
    • Your function finishes, and now the UI thread is free, and it can finally process the updates and redraw itself.

This is why you see a pause and then all 3 updates together.

What you need to do is make the code that takes a long time run on a different thread so that the UI thread can remain free to redraw and update when you'd like it to. This sample program works for me - it requires .NET 4.5 to compile and run

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");
          }
       }
    }
}

What happens here is that we use the C# magical async feature, and the order of operations goes like this:

  • You click a button
  • The UI thread gets a button click message, and invokes your click handler function
  • Instead of calling Operation1 directly, we pass it to Task.Run . This helper function will run your Operation1 method on a thread pool thread.
  • We use the magic await keyword to wait for the thread pool to finish executing operation1. What this does behind the scenes is something morally equivalent to this:
    • suspend the current function - and thus free up the UI thread to re-draw itself
    • resume when the thing we're waiting for completes

Because we're running the long operations in the thread pool now, the UI thread can draw it's updates when it wants to, and you'll see the messages get added as you'd expect.

There are some potential drawbacks to this though:

  1. Because your Operation1 method is Not running on the UI thread, if it needs to access any UI related data (for example, if it wants to read some text from a textbox, etc), it can no longer do this. You have to do all the UI stuff first, and pass it as a parameter to the Operation1 method
  2. It's generally not a good idea to put things that take a long time (more than say 100ms) into the thread pool, as the thread pool can be used for other things (like network operations, etc) and often needs to have some free capacity for this. If your app is just a simple GUI app though, this is unlikely to affect you.
    If it is a problem for you, you can use the await Task.Factory.StartNew<bool>(_ => Operation1(), null, TaskCreationOptions.LongRunning))) instead and each task will run in it's own thread and not use the thread pool any more. It's a bit uglier though :-)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM