0
votes

What is the recommended way to do multithreading with MVVM Light. I have a model which has a bool property Busy

 public bool Busy
    {
        get { return busy_; }
        set
        {
           Set(nameof(Busy), ref busy_, value, broadcast: true);
        }
    }

My view model publish the model directly for the view (the model is inherit MVVM Light's ViewModelBase), so the view binds directly to the model's busy property.

If I call the model always from the UI thread everything is good. But if I do the following in my view model so it may execute on a different thread

Task.Factory.StartNew(() => 
{            
  model_.SomeFunctionThatWillSetBusyDuringItsExecution();
});

Then of course Busy is set from a non UI thread and then the binding fails and the application crashes. If I happen to use the Messenger in the property setter, it seems the Messenger does not automatically dispatch the Messenger handler code to the UI thread either.

I realized there is a DispatcherHelper in MVVM Light, but for the binding it does not seem to help. If I change the property to

    public bool Busy
    {
        get { return busy_; }
        set
        {
           DispatcherHelper.CheckBeginInvokeOnUI(() =>
           {
              Set(nameof(Busy), ref busy_, value, broadcast: true);
           });
        }
    }

I still get an exception and the application crash due to the binding source is not on the correct thread. So my question is simple, what is the recommended way to do multithreading like this in MVVM Light?

I did also try to use a syncronizationContext.

           syncContext_.Post(() =>
           {
              Set(nameof(Busy), ref busy_, value, broadcast: true);
           }, null);

That works if the call is always from a non UI-thread. If the call is already from the UI thread, the syncContext.Post results in that the Set() function is not called until all the code in the ViewModel method has finished. That means the busy state might not be updated correctly for the remaining code. So it is not an ideal solution.

I am thankful for help on this topic.

1
I think the issue is that your Set accessor should not be performing such operation. I believe there is a design problem here. Your "IsBusy" should only be set to false/true on some event/condition of your thread method (in the UI thread). This way you wouldn't even need to bother with DispatcherHelper or thread access issues. The thread method should always be isolated and only report a start/finish condition and progress update, all of which should always happens in the main/UI thread.Luishg

1 Answers

1
votes

Instead of adding the DispatcherHelper code inside the property I added it at all places where the property was modified. Then it seems to work well.

Only problem, since one dispatch the work to the UI thread, the code in the ViewModel would not get the updated state if part of the view model method already runs on the UI thread. I found a way to force the UI thread to process its messenger queue though making sure it got the updated state of Busy. It is not the best looking solution, and it is likely to have a bad performance impact due to all context switching, but at least it works and it is a simple one liner.

Code to force the UI thread to process all messages in its queue

  DispatcherHelper.UIDispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle, null);

If there is a more optimal way to solve it then please let me know. Otherwise I will set this as the answer in a few days from now.