I use ViewModels with databinding directly to properies in the ViewModel itself. In some cases, the properties are updated on a different thread. To avoid crashing I make use of the Dispatcher. When the ViewModel is instantiated it captures the current dispatcher and can then use it later as needed.
The one assumption I make is that the ViewModel itself is created on the main thread, and this is easily guaranteed as my ViewModels are always created in the constructor of the associated view (Form/Control), which always runs on the UI thread by design.
I created helper methods to set a property value. This helper calls RaisePropertyChanged. I made a "thread safe" override that can be used to ensure the raised event is fired on the main thread. When done this way, the UI component that is bound to the property will update itself on the UI thread even though the propery was updated on a different thread.
So for me it looks something like this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Dispatcher _dispatcher;
public ViewModelBase()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
protected void RaisePropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
RaisePropertyChanged(propertyName);
return true;
}
protected bool SetFieldOnMainThread<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
RunOnUiThread(() => RaisePropertyChanged(propertyName));
return true;
}
protected void RunOnUiThread(Action action)
{
if (action != null)
{
_dispatcher.Invoke(action);
}
}
}
// Used like this:
public class TestViewModel : ViewModelBase
{
private string _name;
public string Name {
get => _name;
set => SetFieldOnMainThread(ref _name, value);
}
}