繁体   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