2011年4月29日星期五

Sync Notify Pattern for WPF Cross Thread View Model

WPF has a powerful data binding mechanism, by which it is easy to implement MVVM and MVC pattern for UI application.
But thing gets not so beautiful once you introduced multi-thread into the app. Somehow, it means you have to manually call dispatcher method to synchronize the thread context, or else INotifyPropertyChanged contract causes cross thread violation, which is likely to drive UI component throw exception. But tons of calls to Dispatcher.Invoke or Dispatcher.BeginInvoke make you code ugly and hard to maintain. And it is boring to call dispatcher everytime you try to write view model properties.

Is there any solution to this embarrassed situation?
The answer is Sync Notify pattern.

By analyzing the WPF data binding data flow, you can find the best point to behave the thread synchronization is at the boundary of your code and WPF implementation, which is the property changed  event raiser. To sync the thread context here makes your code clean and effective.

Typically, you might implement the INotifyPropertyChanged in following way:

        #region INotifyPropertyChanged Members         public event PropertyChangedEventHandler PropertyChanged;         protected void Notify(string propertyName)         {             if (PropertyChanged != null)                 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));         }         #endregion
And you might implement the property of view model as following:
        #region Notify Property ViewModelProperty         private string viewModelPropertyBackField;         public string ViewModelProperty         {             get { return viewModelPropertyBackField; }             set             {                 if (viewModelPropertyBackField == value)                     return;                 viewModelPropertyBackField = value;                 Notify("ViewModelProperty");             }         }         #endregion
This implementation works perfect in single thread context , but fails in multi-thread context. So we introduce a new event raiser implementation, which synchronize the thread context before raising the event:
        #region Sync INotifyPropertyChanged Members         protected void SyncNotify(string propertyName, bool wait = false, Dispatcher dispatcher = null)         {             if (PropertyChanged == null)                 return;             dispatcher = dispatcher ?? System.Windows.Threading.Dispatcher.CurrentDispatcher;             if (dispatcher.Thread == Thread.CurrentThread)             {                 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));             }             else             {                 if (wait)                 {                     dispatcher.Invoke(PropertyChanged, this, new PropertyChangedEventArgs(propertyName));                 }                 else                 {                     dispatcher.BeginInvoke(PropertyChanged, this, new PropertyChangedEventArgs(propertyName));                 }             }         }         

Posted via email from 米良的实验室

没有评论:

发表评论