[英]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:
这是怎么回事:
RichTextBox
RichTextBox
RichTextBox
RichTextBox
RichTextBox
RichTextBox
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
功能,操作顺序如下:
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
方法。 await
keyword to wait for the thread pool to finish executing operation1. await
关键字等待线程池完成执行operation1。 What this does behind the scenes is something morally equivalent to this: 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: 但是,这有一些潜在的缺点:
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 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.