16

So say in an MVVM environment, I'm in a background thread and I'd like to run an update on a ui control. So normally I'd go myButton.Dispatcher.BeginInvoke(blabla) but I don't have access to myButton (because the viewmodel doesn't have access to the view's controls). So what is the normal pattern for doing this?

(I guess there's always binding, but I'd like to know how to do it via the dispatcher)

Shai UI
  • 50,568
  • 73
  • 204
  • 309
  • 2
    Duplicate of http://stackoverflow.com/questions/486758/is-wpf-dispatcher-the-solution-of-multi-threading-problems ? – Andrew T Finnell Jan 07 '11 at 01:07
  • Not a duplicate...he's asking how to get a dispatcher from a background thread launched by a ViewModel (which normally doesn't have access to a dispatcher). – Michael Brown Jan 10 '11 at 23:38

7 Answers7

37

I usually use Application.Current.Dispatcher: since Application.Current is static, you don't need a reference to a control

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • 7
    And how will you unit test your view models without an application object? – Geert van Horrik Jan 13 '11 at 09:13
  • 2
    @Geert van Horrik You could mock the application object for unit testing your view models. Sorry for the necro comment - I just stumbled on this SO article while searching for a valid MVVM solution for multithreaded programming. – SRM Jun 13 '11 at 22:27
  • 1
    @aaronburro nothing prevents you from putting it behind an abstraction that you can mock in unit tests. – Thomas Levesque Sep 14 '20 at 07:03
  • I think you've even got a nice blog post about doing exactly that – aaronburro Sep 15 '20 at 16:05
15

From Caliburn Micro source code :

public static class Execute
{
    private static Action<System.Action> executor = action => action();

    /// <summary>
    /// Initializes the framework using the current dispatcher.
    /// </summary>
    public static void InitializeWithDispatcher()
    {
#if SILVERLIGHT
        var dispatcher = Deployment.Current.Dispatcher;
#else
        var dispatcher = Dispatcher.CurrentDispatcher;
#endif
        executor = action =>{
            if(dispatcher.CheckAccess())
                action();
            else dispatcher.BeginInvoke(action);
        };
    }

    /// <summary>
    /// Executes the action on the UI thread.
    /// </summary>
    /// <param name="action">The action to execute.</param>
    public static void OnUIThread(this System.Action action)
    {
        executor(action);
    }
}

Before using it you'll have to call Execute.InitializeWithDispatcher() from the UI thread then you can use it like this Execute.OnUIThread(()=>SomeMethod())

Catalin DICU
  • 4,610
  • 5
  • 34
  • 47
  • I still get the error if I use Dispatcher.CurrentDispatcher. It works correctly if I use Application.Current.Dispatcher. – Ken Smith Jun 12 '12 at 03:19
5

I tend to have my ViewModels inherit from DependencyObject and ensure that they are constructed on the UI thread, which poises them perfectly to handle this situation - they have a Dispatcher property that corresponds to the UI thread's dispatcher. Then, you don't need to pollute your view with the ViewModel's implementation details.

Some other pluses:

  • Unit testability: you can unit test these without a running application (rather than relying on Application.Current.Dispatcher)
  • Loose coupling between View & ViewModel
  • You can define dependency properties on your ViewModel and write no code to update the view as those properties change.
FMM
  • 4,289
  • 1
  • 25
  • 44
  • DependencyObject is quite heavy, what you really need is DispatcherObject. It has a simple implementation: its constructor calls Dispatcher.CurrentDispatcher and stores the value in a field for later use. If you cannot add a base class to your ViewModel, you can easily implement what DispatcherObject is doing. – Tomas Karban Jul 14 '21 at 10:49
4

The ViewModelBase of Catel has a Dispatcher property that you can use.

Geert van Horrik
  • 5,689
  • 1
  • 18
  • 32
  • GetCurrentDispatcher method in DispatcherHelper class. https://catel.codeplex.com/SourceControl/latest#src/Catel.MVVM/Catel.MVVM.NET40/Windows/Threading/Helpers/Dispatcherhelper.cs – Der_Meister Mar 23 '14 at 03:39
0

You could raise an event on your View Model (perhaps using a naming convention to indicate it's going to be raised from a non-UI thread - e.g. NotifyProgressChangedAsync). Then your View whom is attached to the event can deal with the dispatcher appropriately.

Or, you could pass a delegate to a synchronizing function to your View Model (from your View).

Reddog
  • 15,219
  • 3
  • 51
  • 63
0

Pass the UI thread's dispatcher to the ViewModel's constructor and store it in the VM.

Take note that each thread may have its own dispatcher. You are going to need the UI thread's!

Helge Klein
  • 8,829
  • 8
  • 51
  • 71
  • The fact that there may be multiple Dispatchers you have to worry about (while a bad design idea, but it happens) suggests that passing in a Dispatcher is a bad idea. – aaronburro Sep 12 '20 at 00:00
0

Most of the time you don't need the Dispatcher in a ViewModel (> 99% of the time). Earlier versions of .NET didn't marshal PropertyChanged events to the UI thread appropriately, which caused issues. There was a way around it, which required you to raise that event in a way that was aware of the Dispatcher and could automatically marshal when needed. .NET 3.5 and above do this automatically now.

Because the Dispatcher is a UI concept, its presence in a ViewModel is a huge code smell. It suggests that you are doing something wrong. More likely, you either need to inject something which abstracts your ViewModel from the UI resource you are manipulating (changing the mouse cursor is a good example), or you've actually coupled the View to the ViewModel. In the latter case, you can usually fix this with some kind of attached behaviour which will subscribe to events and property changes on your ViewModel.

There is a hangup with this... CollectionChanged does have thread affinity (indirectly through CollectionViews which get automatically created by WPF), and the best way around this is to check for SynchronizationContexts on the event delegate subsribers when raising that event. It stinks, but it still doesn't require you to pass the Dispatcher to the VM.

aaronburro
  • 504
  • 6
  • 15