0

I have the following code in a message handler (that can be invoked on any thread):

private readonly Dictionary<string,IView> _openedViews = new Dictionary<string,IView>();
private readonly object _lockObject = new object();

public MainView() 
{
    Messenger.Default.Register<ViewChangeMessage>(this, "adminView", m =>
    {
         var key = m.ViewName;
         lock (_lockObject)
         {
            if (_openedViews.ContainsKey(key) == false)
                _openedViews.Add(key, GetView(key));
           content.Content = _openedViews[key];
         }
         //...
    });
    //...

How can I still get this exception: An element with the same key already exists in the System.Collections.Generic.Dictionary<TKey,TValue>.

The exception is produced if I rapidly cause the message to be sent multiple times.

EDIT: added more context to the code, Messenger is from Galasoft.MVVMLight

TDaver
  • 7,164
  • 5
  • 47
  • 94

4 Answers4

1

Well in that code you posted I don't see any data race.

If GetView cannot cause a data race you could try to replace that entire block of locked code with a ConcurrentDictionary.GetOrAdd:

private readonly ConcurrentDictionary<string,IView> _openedViews = 
          new ConcurrentDictionary<string,IView>();

public MainView() 
{
    Messenger.Default.Register<ViewChangeMessage>(this, "adminView", m =>
    {
         var key = m.ViewName;
         content.Content = _openedViews.GetOrAdd(key, GetView(key));
         //...
    });
    //...
Tudor
  • 61,523
  • 12
  • 102
  • 142
  • Better yet, use the `GetOrAdd` overload that accepts a `Func` to avoid unnecessary instantiation of the view – Steve Greatrex Jun 13 '12 at 07:53
  • @Tudor: Thanks for reassuring my thinking, that there's no real race condition in my code. It was really a thread affinity + dispatcher mixup, see my posted answer – TDaver Jun 13 '12 at 13:21
0

Have you made sure that all threads are using the same instance of lockObject? If they're not then it won't stop multiple threads getting to your add code.

Andy
  • 6,366
  • 1
  • 32
  • 37
  • given that both _openedViews and _lockObject are private readonly instance fields of the same class, no, I don't think so. – TDaver Jun 13 '12 at 07:13
0

Move var key = m.ViewName; inside lock statement.

Grzegorz W
  • 3,487
  • 1
  • 21
  • 21
0

Here is what happened: the GetView instantiated a view, which had a long running operation somewhere (waiting on a background thread), and so that waiting wouldn't lock the UI up someone introduced this code:

public static void DoEvents()
{
    DispatcherFrame frame = new DispatcherFrame();
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
        new DispatcherOperationCallback(ExitFrame), frame);
    Dispatcher.PushFrame(frame);
}

and thanks to the PushFrame, a second message was handled ON THE SAME THREAD as the first, hence the lock did nothing to stop it.
Once I've rearranged the code to this, the problem went away:

if (_openedViews.ContainsKey(key) == false)
{
   _openedViews.Add(key, null);
   _openedViews[key] = ServiceRegistry.GetService<IShellService>().GetView(key);
}
TDaver
  • 7,164
  • 5
  • 47
  • 94