2
votes

Here is basically what is happening....

  1. Class A (Main thread) sends an MVVM message
    • This message is received, and in the course of processing, Class B is constructed and kicks off a background task.
    • This background sends an seperate MVVM message.
    • Class C has registered for this message and does an invoke on the dispatcher to attempt to update the UI.
    • At this point the main thread is still executing the original Send command and the threads are deadlocked (I can pause the debugger and see they are both waiting).

Other Notes

  1. If I add a sleep in the background thread for one second (allowing the main thread's Send method to complete) it works fine.
  2. This only happens if there is a nested MVVM message sent on another thread which invokes on the dispatcher.
    • Commenting out the dispatcher call...fine.
    • Not using an MVVM message to invoke the dispatcher...fine.

Can anyone explain what is going on?

1

1 Answers

2
votes

I'll take a stab at this...

You can take a look at the MVVM-Light source code on its CodePlex site. I'm going to paste in the relevant method here (slightly annotated for the sake of this post):

    private void SendToTargetOrType<TMessage>(TMessage message, Type messageTargetType, object token)
    {
        var messageType = typeof(TMessage);

        if (_recipientsOfSubclassesAction != null)
        {
            // Clone to protect from people registering in a "receive message" method
            // Correction Messaging BL0008.002
            var listClone =
                _recipientsOfSubclassesAction.Keys.Take(_recipientsOfSubclassesAction.Count()).ToList();

            foreach (var type in listClone)
            {
                List<WeakActionAndToken> list = null;

                if (messageType == type
                    || messageType.IsSubclassOf(type)
                    || type.IsAssignableFrom(messageType))
                {
                    lock (_recipientsOfSubclassesAction)
                    {
                        list = _recipientsOfSubclassesAction[type].Take(_recipientsOfSubclassesAction[type].Count()).ToList();
                    }
                }

                // Class A probably sends a message here from the UI thread
                SendToList(message, list, messageTargetType, token);
            }
        }

        if (_recipientsStrictAction != null)
        {
            // Class B grabs this lock on the background thread.
            // Class A continues processing on the UI thread and arrives here.
            // An attempt is made to grab the lock on the UI thread but it is
            // blocked by the background thread & Class B which in turn is waiting
            // on the UI thread. And here you have yourself a deadlock
            lock (_recipientsStrictAction)
            {
                if (_recipientsStrictAction.ContainsKey(messageType))
                {
                    var list = _recipientsStrictAction[messageType]
                        .Take(_recipientsStrictAction[messageType].Count())
                        .ToList();

                    // Class B sends its message here.
                    // Class C receives the message and does an Invoke on the UI thread
                    SendToList(message, list, messageTargetType, token);
                }
            }
        }

        RequestCleanup();
    }
  1. Class A probably sends a message on the UI thread picked up by 'subclass recipients'.
  2. Class B is a recipient that picks up this message and kicks off your background task.
  3. Your background task then sends a message that has a 'strict action recipient'.
  4. Class B grabs the '_recipientsStrictAction' lock on the background thread.
  5. Class B sends the message to class C, which does an invoke on the UI thread.
  6. This invoke blocks because the UI thread is still executing the first message.
  7. UI thread execution continues on and then tries to grab the '_recipientsStrictAction' lock on the UI thread. Unfortunately, your background thread (which is waiting on the UI thread) already has the lock. You are now deadlocked :(

Might want to consider doing an InvokeAsync in Class C rather than an Invoke. I think you could probably avoid the issue that way.

Makes me wonder why MVVM light is sending the message 'inside' the lock. Seems like a not-so-cool sort of thing to do. After typing all this up, I went looking around the CodePlex site, looks like this is issue has been documented: http://mvvmlight.codeplex.com/workitem/7581