1

I've created an application that reads properties from files using the Windows-API-Code-Pack from this package. I'm having an issue when retrieving properties

var width = fileInfo.Properties.GetProperty(SystemProperties.System.Video.FrameWidth).ValueAsObject;

The code breaks here giving me

System.ArgumentException: An item with the same key has already been added.
   at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at Microsoft.WindowsAPICodePack.Shell.PropertySystem.ShellPropertyFactory.GenericCreateShellProperty[T](PropertyKey propKey, T thirdArg)
   at Microsoft.WindowsAPICodePack.Shell.PropertySystem.ShellProperties.GetProperty(PropertyKey key)

This happens mostly when calling this portion of a code in a PLINQ

.AsParallel().WithDegreeOfParallelism(_maxConcurrentThreads).ForAll(...)

even if the degree is set to 1. How can I solve it?

AzeExMachina
  • 104
  • 14
  • It sounds like the library isn't thread safe. Do you see it if you don't use PLinq at all? – stuartd Apr 11 '19 at 15:00
  • I've done many tries but never saw it when not using PLINQ. If it's not thread safe, what could I do to make it safe? Can I use locks around my code? Or I would need to modify the source? – AzeExMachina Apr 11 '19 at 15:48
  • I would suggest downloading the code from the repo and referencing that instead of the package, and debugging it, then you'll be able to see what the actual problem is. – stuartd Apr 11 '19 at 15:58

2 Answers2

1

To extend on your existing answer, switching the Dictionary to a ConcurrentDictionary would also solve the problem and remove the need for locks.

    private static ConcurrentDictionary<int, Func<PropertyKey, ShellPropertyDescription, object, IShellProperty>> _storeCache
        = new ConcurrentDictionary<int, Func<PropertyKey, ShellPropertyDescription, object, IShellProperty>>();
...

    private static IShellProperty GenericCreateShellProperty<T>(PropertyKey propKey, T thirdArg)
    {
       ...

        Func<PropertyKey, ShellPropertyDescription, object, IShellProperty> ctor;
        ctor = _storeCache.GetOrAdd((hash, (key, args) -> {
            Type[] argTypes = { typeof(PropertyKey), typeof(ShellPropertyDescription), args.thirdType };
            return ExpressConstructor(args.type, argTypes);
        }, {thirdType, type});

        return ctor(propKey, propDesc, thirdArg);
    }
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
0

Following stuartd suggestion I was able to solve this issue by modifying the source code of the package and adding locks in this code at lines 57 and 62, like this

lock (_storeCache)
{
    if (!_storeCache.TryGetValue(hash, out ctor))
    {
        Type[] argTypes = { typeof(PropertyKey), typeof(ShellPropertyDescription), thirdType };
        ctor = ExpressConstructor(type, argTypes);
        lock (_storeCache)
            _storeCache.Add(hash, ctor);
    }
}
AzeExMachina
  • 104
  • 14
  • 1
    The inner lock is unnecessary, you are already in a exclusive section from the outer lock, the inner lock does nothing. A better idea might be to get rid of the lock and replace the `Dictionary` with a [`ConcurrentDictionary`](https://learn.microsoft.com/en-us/dotnet/api/System.Collections.Concurrent.ConcurrentDictionary-2?view=netframework-4.7.2) – Scott Chamberlain Apr 12 '19 at 08:20