2

Recently I had a need for a Dictionary that I could update from multiple threads, and the obvious candidate for this job was the build-in ConcurrentDictionary. Unfortunately I ended up not using it, and using instead a normal Dictionary protected with a lock, because of a fatal limitation: the TryRemove does not offer an overload that allows the conditional removal of an element. The available TryRemove method removes and returns the removed element, but in my case it was mandatory to remove the element only if it was inserted earlier by the same workflow. Removing an element from a different workflow (even for a fraction of a μsec) could have undesirable consequences that I would prefer not to have to resolve. So my question is: Is it possible to amend the existing ConcurrentDictionaryclass with a thread-safe conditional TryRemove extension method?

For reference here is my use case. The dictionary is populated safely using the AddOrUpdate method:

var dict = new ConcurrentDictionary<string, CancellationTokenSource>();
var cts = dict.AddOrUpdate("Key1", key => new CancellationTokenSource(),
    (key, existingValue) =>
{
    existingValue.Cancel();
    return new CancellationTokenSource();
});

Later I would like to remove the value I inserted earlier by calling the non-existent method below:

var removed = dict.TryRemove("Key1", (key, existingValue) =>
{
    return existingValue == cts;
});

This is the signature of the needed method:

public static bool TryRemove<TKey, TValue>(
    this ConcurrentDictionary<TKey, TValue> source, TKey key,
    Func<TKey, TValue, bool> predicate)
{
    // Is it possible?
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • 1
    I don't think this is possible. The only thing I can think of is wrapping the `ConcurrentDictionary` in a class that uses a lock to make the method thread safe...which defeats the point. – Tim Pohlmann Sep 24 '19 at 13:11
  • @TimPohlmann I am pessimist about this too. Even your suggestion of a wrapper class would require not only the `TryRemove` but ALL methods of the wrapper to operate under the same `lock`, which defeats the purpose of the class entirely. – Theodor Zoulias Sep 24 '19 at 13:18
  • with "in my case it was mandatory to remove the element only if it was inserted earlier by the same workflow" do you mean that only the thread that inserted an element is allowed to remove it, but any thread is allowed to access the element? – Ackdari Sep 24 '19 at 13:19
  • @Ackdari actually in my case I was in the mid of an `async` method, before and after an `await`. This is why I talked about "workflows" instead of threads, because the same workflow runs later in a different thread. To answer your question, all workflows can update any element (replacing it with a different one), but only the workflow that created the element is allowed to remove it without replacement. – Theodor Zoulias Sep 24 '19 at 13:25

1 Answers1

0

I think that you need to implement your own Dictionary that somehow remembers which Task added which element.

Disclaimer

But I would recommend to remove what ever detail you have that leaded to this requirement since it sounds somewhat fishy to me that elements can only be removed by its creating workflow.

I say that because what if workflow 1 inserted the element elm1 and didn't removed it while it was working. So the element elm1 remains in the dictionary and nobody can remove it since its creating workflow has ended.

Community
  • 1
  • 1
Ackdari
  • 3,222
  • 1
  • 16
  • 33
  • Actually an orphan element (if it was possible to become orphan, which is very unlikely) can still be removed. Another workflow could first replace the element, and then remove the replacement. – Theodor Zoulias Sep 24 '19 at 13:50
  • @TheodorZoulias but then the question of who can remove an element remains. Can only the original creator of an entry remove it or can only the workflow who lastly replaced an element remove it or can every workflow who replaced the element remove it?. – Ackdari Sep 24 '19 at 14:39
  • Replacing an element has the effect of removing it from the dictionary. A workflow is allowed to remove a "foreign" element by replacing it with its own element in an atomic operation (with `AddOrUpdate`). But it's not allowed to remove a foreign element without replacing it. This requirement may sound strange, but it is actually quite reasonable, and if you delved into the details of my case I think you would agree. If you are interested, it is [here](https://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice/58079327#58079327). – Theodor Zoulias Sep 24 '19 at 15:11