Dispatching to the UI Thread
When you are building rich client user interfaces you have to be vigilant not to lock up the user interface while performing intensive background tasks.
Using BackgroundWorker
One of the techniques available is to make use of the built in .NET BackgroundWorker object. This allows you to remove background processing
from the UI thread. As you can see in the example below there are a lot of pieces that you need to make this work.
public partial class CustomerEdit : Form { private readonly BackgroundWorker _backgroundWorker; public CustomerEdit() { InitializeComponent(); _backgroundWorker = new BackgroundWorker(); _backgroundWorker.DoWork += DoSave; _backgroundWorker.RunWorkerCompleted += SaveCompleted; } private void SaveCompleted(object sender, RunWorkerCompletedEventArgs e) { lblStatus.Text = "Finished Saving"; } private void btnSave_Click(object sender, EventArgs e) { _backgroundWorker.RunWorkerAsync(); } private void DoSave(object sender, DoWorkEventArgs doWorkEventArgs) { var customerRepository = new CustomerRepository(); customerRepository.Save(BuildCustomerFromScreen()); } private Customer BuildCustomerFromScreen() { return new Customer(); } }
To properly manage and interact with a BackgroundWorker process you need to hook the
DoWork and RunWorkerCompleted events. You need to couple the code that should be run to the
DoWorker event handler. The code to update the UI needs to be coupled to the
RunWorkerCompleted event handler and, finally, you need to execute the
BackgroundWorker object's RunWorkerAsync method to kick off the whole process.
As you can see, there are a lot of moving pieces in a very simple BackgroundWorker example. Here's how PostSharp
simplifies this common coding scenario.
Adding background processing
- The first thing you need to understand is that you can write your code as if you had no thoughts of having any background
processing occuring.
public partial class CustomerEdit : Form { public CustomerEdit() { InitializeComponent(); } private void SaveCompleted() { lblStatus.Text = "Finished Saving"; } private void btnSave_Click(object sender, EventArgs e) { DoSave(); SaveCompleted(); } private void DoSave() { var customerRepository = new CustomerRepository(); customerRepository.Save(BuildCustomerFromScreen()); } private Customer BuildCustomerFromScreen() { return new Customer(); } }
- Once you have written your code you will need to determine which piece of it should run in the background and in the UI
threads. In this example the
btnSave_Clickmethod should execute in the background and theSaveCompletedmethod should execute in the UI thread.
First, you should add the background processing to thebtnSave_Clickmethod. - You will be prompted with a wizard to complete the process. Accept the first page.
- If you have not yet added background processing to your project, PostSharp will download the Threading Pattern Library and
add it for you.
- Click Finish to complete the process.
- When you look at your code you will see that only one thing has changed. The method you designated to run in the background
is now decorated with the
[BackgroundMethod]attribute.[BackgroundMethod] private void btnSave_Click(object sender, EventArgs e) { DoSave(); SaveCompleted(); }
Adding UI thread processing
Once you've added background processing to the button click, you will need a way to have the
SaveCompleted method run on the UI thread. If you don't do this you will get an
InvalidOperationException because setting the lblStatus.Text property is a cross threading operation.
Here's how you can fix this.
- Your code has encapsulated the UI thread interaction in the
SaveCompletedmethod. Open the smart tag on that method and choose to execute the method in the object thread. This is tell PostSharp to configure this method to execute in the thread that the class containing the method is executing in. - After selecting the smart tag option you will be returned to your code and you'll notice that the only change was the addition
of the
[DispatchedMethod]attribute to theSaveCompletedmethod.[DispatchedMethod] private void SaveCompleted() { lblStatus.Text = "Finished Saving"; }
Now if you run your code you will no longer receive the InvalidOperationException and instead will see the label on the
UI update. All of the
Save functionality will occur in a separate thread which prevents the user interface from locking up while that
is happening.
What to read next?
Background Thread Execution or back to Threading.