Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Threading errors with Application.LoadComponent (key already exists)

Tags:

MSDN says that public static members of System.Windows.Application are thread safe. But when I try to run my app with multiple threads I get the following exception:

ArgumentException: An entry with the same key already exists.     at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)    at System.Collections.Generic.SortedList`2.Add(TKey key, TValue value)    at System.IO.Packaging.Package.AddIfNoPrefixCollisionDetected(ValidatedPartUri partUri,         PackagePart part)    at System.IO.Packaging.Package.GetPartHelper(Uri partUri)    at System.IO.Packaging.Package.GetPart(Uri partUri)    at System.Windows.Application.GetResourceOrContentPart(Uri uri)    at System.Windows.Application.LoadComponent(Uri resourceLocator, Boolean  bSkipJournaledProperties)        at System.Windows.Application.LoadComponent(Uri resourceLocator) 

The exception occurs on the following call:

genericResources = (ResourceDictionary)Application.LoadComponent(new Uri("/Themes/Generic.xaml", UriKind.Relative)); 

The application works fine on a single thread and even on two or three. When I get up past 5 then I get the error every time. Am I doing something wrong? What can I do to fix this?

like image 364
Kelly Avatar asked Mar 17 '10 16:03

Kelly


1 Answers

You are not doing something wrong. MSDN is wrong. Application.LoadComponent is not actually thread safe. This is a bug in WPF, in my opinion.

The problem is that whenever Application.LoadComponent loads a "Part" from a "Package" it:

  1. Checks its internal cache for the package to see if the part is already loaded & returns it if found
  2. Loads the part from the file
  3. Adds it to the internal cache
  4. Returns it

You have two threads calling Application.LoadComponent to load the same part at the same time. The MSDN documentation says this is ok, but what is happening is:

  1. Thread #1 checks the cache and starts loading from the file
  2. Thread #2 checks the cache and starts loading from the file
  3. Thread #1 finishes loading from the file and adds to the cache
  4. Thread #2 finishes loading from the file and tries to add to the cache, resulting in a duplicate key exception

The workaround for the bug is to wrap all calls to Application.LoadComponent inside a lock().

Your lock object can be created thusly in your App.cs or elsewhere (your choice):

 public static object MyLoadComponentLock = new Object(); 

Then your LoadComponent call looks like this:

 lock(App.MyLoadComponentLock)    genericDictionary = (ResourceDictionary)Application.LoadComponent(... 
like image 119
Ray Burns Avatar answered Sep 29 '22 14:09

Ray Burns