Application.LoadComponent的线程错误(密钥已存在)

MSDN说System.Windows.Application的公共静态成员是线程安全的。 但是当我尝试使用多个线程运行我的应用程序时,我得到以下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) 

以下调用发生exception:

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

该应用程序在单个线程上工作正常,甚至在两个或三个上。 当我从5点起床后,每次都会收到错误。 难道我做错了什么? 我该怎么做才能解决这个问题?

你没有做错事。 MSDN错了。 Application.LoadComponent实际上并不是线程安全的。 在我看来,这是WPF中的一个错误。

问题是每当Application.LoadComponent从“Package”加载“Part”时:

  1. 检查其内部缓存以查看该部件是否已加载并在找到时将其返回
  2. 从文件中加载零件
  3. 将其添加到内部缓存
  4. 返回它

您有两个线程调用Application.LoadComponent来同时加载相同的部分。 MSDN文档说这没关系,但发生的事情是:

  1. 线程#1检查缓存并从文件开始加载
  2. 线程#2检查缓存并从文件开始加载
  3. 线程#1完成从文件加载并添加到缓存
  4. 线程#2完成从文件加载并尝试添加到缓存,从而导致重复的键exception

该错误的解决方法是将所有对Application.LoadComponent的调用包装在lock()中。

您可以在App.cs或其他地方(您的选择)创建锁定对象:

  public static object MyLoadComponentLock = new Object(); 

然后你的LoadComponent调用如下所示:

  lock(App.MyLoadComponentLock) genericDictionary = (ResourceDictionary)Application.LoadComponent(... 

看起来好像已经在地图中添加了具有相同键的项目。 这不是一个线程问题,这是你所拥有的程序的一个问题。 一个线程已经为地图添加了一个键/值对,第二个线程正在尝试添加一个具有相同键的值,您是如何在两个单独的线程上获得相同的键的? 你是如何生成密钥的?

SortedList对象的元素按键排序,或者根据创建SortedList时指定的特定IComparer实现,或者根据键本身提供的IComparable实现。 在任何一种情况下, SortedList都不允许重复键。

(来自msdn文档)

更新:
在调用LoadComponent时尝试同步并查看问题是否仍然存在。

当他们说出以下内容时,我根本不知道他们的意思:

此类型的公共静态(在Visual Basic中为Shared)成员是线程安全的。 此外,FindResource和TryFindResource方法以及Properties和Resources属性是线程安全的。

它确实说线程安全,但如果它复制相同的密钥,那么它们必须引用其他类型的线程安全。