简体   繁体   English

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

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

What I am trying to achieve is to add text after every operation to a RichTextBox. 我想要实现的是在每次操作后将文本添加到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. 问题是我在操作3完成后查看操作1和2的附加文本。

I read somewhere that I need to use something called BackgroundWorker??? 我在某处读到了我需要使用的东西叫做BackgroundWorker ???

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

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. 您可以执行上述操作3次,或使用ReportProgress在一个后台线程中运行所有任务,并定期报告进度。

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. WPF(以及大多数其他UI框架的工作方式)是有一个UI线程,它处理所有UI事件(例如按钮点击)和UI绘图。

The UI can't draw things if it's busy doing other things. 如果UI忙于做其他事情,UI就无法绘制内容。 What's happening is this: 这是怎么回事:

  • You click a button 您单击一个按钮
  • The UI thread gets a button click message, and invokes your click handler function UI线程获取按钮单击消息,并调用您的单击处理函数
    • Now, the UI can't redraw or perform any other updates until your click handler function finishes. 现在,在单击处理函数完成之前,UI无法重绘或执行任何其他更新。
  • Your Operation1 function finishes, and you append to the RichTextBox 您的Operation1函数完成,并附加到RichTextBox
    • The UI can't update because it's still stuck running your code 用户界面无法更新,因为它仍然无法运行您的代码
  • Your Operation2 function finishes, and you append to the RichTextBox 您的Operation2函数完成,并附加到RichTextBox
    • The UI can't update because it's still stuck running your code 用户界面无法更新,因为它仍然无法运行您的代码
  • Your Operation3 function finishes, and you append to the RichTextBox 您的Operation3函数完成,并附加到RichTextBox
    • Your function finishes, and now the UI thread is free, and it can finally process the updates and redraw itself. 你的函数完成了,现在UI线程是免费的,它最终可以处理更新并重绘自己。

This is why you see a pause and then all 3 updates together. 这就是为什么你看到一个暂停,然后所有3个更新在一起。

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. 您需要做的是使需要很长时间的代码在不同的线程上运行以便UI线程可以保持自由重绘和更新的时间。 This sample program works for me - it requires .NET 4.5 to compile and run 这个示例程序适合我 - 它需要.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");
          }
       }
    }
}

What happens here is that we use the C# magical async feature, and the order of operations goes like this: 这里发生的是我们使用C#魔法async功能,操作顺序如下:

  • You click a button 您单击一个按钮
  • The UI thread gets a button click message, and invokes your click handler function UI线程获取按钮单击消息,并调用您的单击处理函数
  • Instead of calling Operation1 directly, we pass it to Task.Run . 我们不是直接调用Operation1 ,而是将它传递给Task.Run This helper function will run your Operation1 method on a thread pool thread. 这个辅助函数将在线程池线程上运行您的Operation1方法。
  • We use the magic await keyword to wait for the thread pool to finish executing operation1. 我们使用magic await关键字等待线程池完成执行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 暂停当前​​函数 - 从而释放UI线程以重新绘制自身
    • 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. 因为我们现在在线程池中运行长操作,所以UI线程可以在需要时绘制它的更新,并且您将看到消息按照您的预期添加。

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. 因为您的Operation1方法未在UI线程上运行,如果它需要访问任何UI相关数据(例如,如果它想要从文本框中读取某些文本等),则它不能再执行此操作。 You have to do all the UI stuff first, and pass it as a parameter to the Operation1 method 您必须首先执行所有UI内容,并将其作为参数传递给Operation1方法
  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. 将花费很长时间(超过100毫秒)的东西放入线程池通常不是一个好主意,因为线程池可以用于其他事情(如网络操作等)并且通常需要有一些空闲容量为了这。 If your app is just a simple GUI app though, this is unlikely to affect you. 如果你的应用程序只是一个简单的GUI应用程序,这不太可能影响你。
    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. 如果它是一个问题,你可以使用await Task.Factory.StartNew<bool>(_ => Operation1(), null, TaskCreationOptions.LongRunning)))而且每个任务都将在它自己的线程中运行而不是使用线程池了。 It's a bit uglier though :-) 虽然有点丑陋:-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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