Adding text to RichTextBox Async #C / WPF
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20621311/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Adding text to RichTextBox Async #C / WPF
提问by Erez
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.
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:
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.
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???
I read somewhere that I need to use something called BackgroundWorker???
回答by McGarnagle
Using BackgroundWorker, you would just put the background work into DoWork, and the update into RunWorkerCompleted:
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:
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 ReportProgressto run all tasks in one background thread, and periodically report progress.
You could do the above 3 times, or use ReportProgressto run all tasks in one background thread, and periodically report progress.
回答by Orion Edwards
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 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:
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.
- 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.
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 threadso 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
What you need to do is make the code that takes a long time run on a different threadso 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 asyncfeature, and the order of operations goes like this:
What happens here is that we use the C# magical asyncfeature, 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
Operation1directly, we pass it toTask.Run. This helper function will run yourOperation1method on a thread pool thread. - We use the magic
awaitkeyword 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
- You click a button
- The UI thread gets a button click message, and invokes your click handler function
- Instead of calling
Operation1directly, we pass it toTask.Run. This helper function will run yourOperation1method on a thread pool thread. - We use the magic
awaitkeyword 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.
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:
There are some potential drawbacks to this though:
- Because your
Operation1method 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 - 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 theawait 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 :-)
- Because your
Operation1method 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 - 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 theawait 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 :-)

