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”时:
- 检查其内部缓存以查看该部件是否已加载并在找到时将其返回
- 从文件中加载零件
- 将其添加到内部缓存
- 返回它
您有两个线程调用Application.LoadComponent
来同时加载相同的部分。 MSDN文档说这没关系,但发生的事情是:
- 线程#1检查缓存并从文件开始加载
- 线程#2检查缓存并从文件开始加载
- 线程#1完成从文件加载并添加到缓存
- 线程#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属性是线程安全的。
它确实说线程安全,但如果它复制相同的密钥,那么它们必须引用其他类型的线程安全。