Introduction
I was tasked with a small WinForms application a while back which contained a long running calculation. The user would initiate the calculation by pressing a button and when the calculation was complete, the result would then be displayed in a textbox. I also wanted to build the application using different layers so that each layer could vary independently.
Details
I decided to implement this application using the Model-View-Presenter (MVP) pattern. I would set the long running process to run on a different thread (of course) and when the calculation was complete, I would have the presenter update the view. Sounds easy, right? Let’s see.
I used basic data binding to bind a textbox on the view which would display the results:
txtTotal.DataBindings.Add("Text", this, "Total");
The Windows Form itself is the view (implements the IView
interface), so this is actually a self binding.
The Problem
Now here's the issue. If the ‘Total
’ variable was updated on the UI thread, the data binding worked fine and the result was displayed in the textbox. However because I wanted the long running task to be executed on a different thread, when I updated the view from the presenter in a different thread, I received the following error:
'Cross-thread operation not valid: Control 'txtTotal' accessed from a thread other than the thread it was created on'
Every developer has seen this error before and hates it with every 'fiber' of his/her being. No pun intended. Data binding in .NET does not allow updating of data from a different thread. So how can we solve this?
The Solution
There are quite a few solutions to this issue like the BackgroundWorker
component. However the solution I chose involved the SynchronizationContext
object (which a BackgroundWorker
uses behind the scenes. Each thread has SynchronizationContext
associated with it. You can use this context to execute code using a specified thread either using the Post
(asynchronous) or Send
(synchronous) methods.
To get a reference to the SynchronizationContext
, you can use the following code:
SynchronizationContext.Current;
I simply use the view’s SynchronizationContext
object whenever I update any databinded property on the view from the presenter. Here is my code for the presenter:
internal class Presenter : IPresenter
{
public Presenter(IView view)
{
this.View = view;
this.Context = this.View.Context;
this.View.OnLongRunningCalculationRequested +=
new EventHandler(View_OnLongRunningCalculationRequested);
}
private IView View
{
get;
set;
}
private SynchronizationContext Context
{
get;
set;
}
private void View_OnLongRunningCalculationRequested(object sender, EventArgs e)
{
Thread thread = new Thread(RunLongRunningProcess);
thread.Start();
}
private void RunLongRunningProcess()
{
if (Context != null)
{
decimal data = 1000 + 10;
Context.Post(delegate
{
View.Total = data;
}, null);
}
}
}
Notice I update the Total
property of the view using its own SynchronizationContext
. That means the actual delegate gets called from the UI thread. And now, the view's binding to the 'Total
' property works as expected.
Have fun and send any comments my way.
History
- 15th March, 2010: Initial post