Using INotifyPropertyChanged in background threads
- by digitaldias
Following up on a previous blog post where I exemplify databinding to objects, a reader was having some trouble with getting the UI to update. Here’s the rough UI: The idea is, when pressing Start, a background worker process starts ticking at the specified interval, then proceeds to increment the databound Elapsed value. The problem is that event propagation is limeted to current thread, meaning, you fire an event in one thread, then other threads of the same application will not catch it. The Code behind So, somewhere in my ViewModel, I have a corresponding bethod Start that initiates a background worker, for example: public void Start( )
{
BackgroundWorker backgroundWorker = new BackgroundWorker( );
backgroundWorker.DoWork += IncrementTimerValue;
backgroundWorker.RunWorkerAsync( );
}
protected void IncrementTimerValue( object sender, DoWorkEventArgs e )
{
do
{
if( this.ElapsedMs == 100 )
this.ElapsedMs = 0;
else
this.ElapsedMs++;
}while( true );
}
Assuming that there is a property:
public int ElapsedMs
{
get { return _elapsedMs; }
set
{
if( _elapsedMs == value )
return;
_elapsedMs = value;
NotifyThatPropertyChanged( "ElapsedMs" );
}
}
The above code will not work. If you step into this code in debug, you will find that INotifyPropertyChanged is called, but it does so in a different thread, and thus the UI never catches it, and does not update.
One solution
Knowing that the background thread updates the ElapsedMs member gives me a chance to activate BackgroundWorker class’ progress reporting mechanism to simply alert the main thread that something has happened, and that it is probably a good idea to refresh the ElapsedMs binding.
public void Start( )
{
BackgroundWorker backgroundWorker = new BackgroundWorker( );
backgroundWorker.DoWork += IncrementTimerValue;
// Listen for progress report events
backgroundWorker.WorkerReportsProgress = true;
// Tell the UI that ElapsedMs needs to update
backgroundWorker.RunWorkerCompleted +=
( sender, e ) =>
{
NotifyThatPropertyChanged( "ElapsedMs" )
};
backgroundWorker.RunWorkerAsync( );
}
protected void IncrementTimerValue( object sender, DoWorkEventArgs e )
{
do
{
if( this.ElapsedMs == 100 )
this.ElapsedMs = 0;
else
this.ElapsedMs++;
// report any progress
( sender as BackgroundWorker ).ReportProgress( 0 );
}while( true );
}
What happens above now is that I’ve used the BackgroundWorker cross thread mechanism to alert me of when it is ok for the UI to update it’s ElapsedMs field.
Because the property itself is being updated in a different thread, I’m removing the NotifyThatPropertyChanged call from it’s Set method, and moving that responsability to the anonymous method that I created in the Start method.
This is one way of solving the issue of having a background thread update your UI. I would be happy to hear of other cross-threading mechanisms for working in a MCP/MVC/MVVM pattern.