Concurrent collection supporting random (FIFO) and specific Remove - c#

I'm writing an application which manages a collection that requires frequent enqueuing and dequeuing of items in a miltithreaded environment. With single threaded, a simple List is probably enough, but concurrent nature of the environment poses some issues.
Here's the summary:
The structure needs to have a bool TryAdd(T) method, preferrably Add(TKey, TValue);
The structure needs to have a T TryRemove() method which takes a random or preferrably the first added item (essentially implementing a FIFO queue);
The structure needs to have a bool TryRemove(T) method, preferrably Remove(TKey);
So far I have three ideas, all with their issues:
Implement a class containing a ConcurrentDictionary<TKey, TValue> and a ConcurrentQueue like this:
internal class ConcurrentQueuedDictionary<TKey, TValue> where TKey : notnull
{
ConcurrentDictionary<TKey, TValue> _dictionary;
ConcurrentQueue<TKey> _queue;
object _locker;
public bool TryAdd(TKey key, TValue value)
{
if (!_dictionary.TryAdd(key, value))
return false;
lock (_locker)
_queue.Enqueue(key);
return true;
}
public TValue TryRemove()
{
TKey key;
lock (_locker) {
if (_queue.IsEmpty)
return default(TValue);
_queue.TryDequeue(out key);
}
TValue value;
if (!_dictionary.Remove(key, out value))
throw new Exception();
return value;
}
public bool TryRemove(TKey key)
{
lock (_locker)
{
var copiedList = _queue.ToList();
if (copiedList.Remove(key))
return false;
_queue = new(copiedList);
}
return _dictionary.TryRemove(key, out _);
}
}
but that will require a Lock on Remove(T) because it demands a full deep copy of the initial Queue without the removed item while disallowing read from other threads, which means that at least Remove() will also have this lock, and this is meant to be an operation carried out often;
Implement a class containing a ConcurrentDictionary<TKey, TValue> and a ConcurrentDictionary<int order, TKey>, where order is defined on TryAdd with two properties _addOrder and _removeOrder like this:
internal class ConcurrentQueuedDictionary<TKey, TValue> where TKey : notnull
{
ConcurrentDictionary<TKey, TValue> _dictionary;
ConcurrentDictionary<int, TKey> _order;
int _addOrder = 0;
int _removeOrder = 0;
public bool TryAdd(TKey key, TValue value)
{
if (!_dictionary.TryAdd(key, value))
return false;
if (!_order.TryAdd(unchecked(Interlocked.Increment(ref _addOrder)), key))
throw new Exception(); //Operation faulted, mismatch of data in _order
return true;
}
public TValue TryRemove()
{
TKey key;
if (!(_order.Count > 0 && _order.Remove(unchecked(Interlocked.Increment(ref _removeOrder)), out key)))
return default(TValue);
return _dictionary[key];
}
public bool TryRemove(TKey key)
{
if (!_order.Remove(_order.Where(item => item.Value.Equals(key)).First().Key, out _))
return false;
if (!_dictionary.Remove(key, out _))
throw new Exception();
return true;
}
}
but I'm pretty sure just voicing this implementation had put me on a psychiatric watchlist somewhere because it's gonna be a masochistic nightmare to make work properly;
Straight up locking a List because locks are necessary for option 1 anyway.
Any ideas? I'm kinda stumped by this issue as I don't have the best grasp on concurrent collections. Do I need a custom IProducerConsumerCollection? Is it even possible to have both random (or queued) and specific access to concurrent collection elements? Have any of you faced this before, maybe I'm looking at the issue wrong?
Edit: typos, formatting

Creating a concurrent structure like this by combining built-in concurrent collections should be close to impossible, provided of course that correctness is paramount and race-conditions are strictly forbidden. The good news is that acquiring a lock a few thousands times per second is nowhere near the limit where contention starts to become an issue, provided that the operations inside the protected region are lightweight (their duration is measured in nanoseconds).
One way to achieve O(1) complexity of operations, is to combine a LinkedList<T> and a Dictionary<K,V>:
/// <summary>
/// Represents a thread-safe first in-first out (FIFO) collection of key/value pairs,
/// where the key is unique.
/// </summary>
public class ConcurrentKeyedQueue<TKey, TValue>
{
private readonly LinkedList<KeyValuePair<TKey, TValue>> _queue;
private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>
_dictionary;
public ConcurrentKeyedQueue(IEqualityComparer<TKey> comparer = default)
{
_queue = new();
_dictionary = new(comparer);
}
public int Count { get { lock (_queue) return _queue.Count; } }
public bool TryEnqueue(TKey key, TValue value)
{
lock (_queue)
{
ref var node = ref CollectionsMarshal
.GetValueRefOrAddDefault(_dictionary, key, out bool exists);
if (exists) return false;
node = new(new(key, value));
_queue.AddLast(node);
Debug.Assert(_queue.Count == _dictionary.Count);
return true;
}
}
public bool TryDequeue(out TKey key, out TValue value)
{
lock (_queue)
{
if (_queue.Count == 0) { key = default; value = default; return false; }
var node = _queue.First;
(key, value) = node.Value;
_queue.RemoveFirst();
bool removed = _dictionary.Remove(key);
Debug.Assert(removed);
Debug.Assert(_queue.Count == _dictionary.Count);
return true;
}
}
public bool TryTake(TKey key, out TValue value)
{
lock (_queue)
{
bool removed = _dictionary.Remove(key, out var node);
if (!removed) { value = default; return false; }
_queue.Remove(node);
(_, value) = node.Value;
Debug.Assert(_queue.Count == _dictionary.Count);
return true;
}
}
public KeyValuePair<TKey, TValue>[] ToArray()
{
lock (_queue) return _queue.ToArray();
}
}
This combination is also used for creating LRU caches.
You can measure the lock contention in your own environment under load, by using the Monitor.LockContentionCount property: "Gets the number of times there was contention when trying to take the monitor's lock." If you see the delta per second to be a single digit number, there is nothing to worry about.
For a version that doesn't use the CollectionsMarshal.GetValueRefOrAddDefault method, and so it can be used on .NET versions older than .NET 6, see the first revision of this answer.

Related

Best way to implement consumer queue that you can remove items from sequentially (.net 6)

new poster here so I hope this makes sense ...
I need to create a collection that I can remove items from in sequence (basically stock market time series data).
The data producer is multi-threaded and doesn't guarantee that the data will come in sequence.
I've looked all around for a solution but the only thing I can come up with is to create my own custom dictionary, using ConcurrentDictionary and implementing the IProducerConsumer interface so it can be used with with BlockingCollection.
The code I have below does work, but produces an error
System.InvalidOperationException: The underlying collection was
modified from outside of the BlockingCollection
when using the GetConsumingEnumerable() for loop, and the next key in the sequence is not present in the dictionary. In this instance I would like to wait for a specified amount of time
and then attempt to take the item from the queue again.
My questions is:
What's the best way to handle the error when there is no key present. At the moment it seems handling the error would require exiting the loop. Perhaps using GetConsumingEnumerable() is not the right way to consume and a while loop would work better?
Code is below - any help/ideas much appreciated.
IProducerConsumer implementation:
public abstract class BlockingDictionary<TKey, TValue> : IProducerConsumerCollection<KeyValuePair<TKey, TValue>> where TKey : notnull
{
protected ConcurrentDictionary<TKey, TValue> _dictionary = new ConcurrentDictionary<TKey, TValue>();
int ICollection.Count => _dictionary.Count;
bool ICollection.IsSynchronized => false;
object ICollection.SyncRoot => throw new NotSupportedException();
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
{
if (array == null)
{
throw new ArgumentNullException("array");
}
_dictionary.ToList().CopyTo(array, index);
}
void ICollection.CopyTo(Array array, int index)
{
if (array == null)
{
throw new ArgumentNullException("array");
}
((ICollection)_dictionary.ToList()).CopyTo(array, index);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return ((IEnumerable<KeyValuePair<TKey, TValue>>)_dictionary).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<KeyValuePair<TKey, TValue>>)this).GetEnumerator();
}
public KeyValuePair<TKey, TValue>[] ToArray()
{
return _dictionary.ToList().ToArray();
}
bool IProducerConsumerCollection<KeyValuePair<TKey, TValue>>.TryAdd(KeyValuePair<TKey, TValue> item)
{
return _dictionary.TryAdd(item.Key, item.Value);
}
public virtual bool TryTake(out KeyValuePair<TKey, TValue> item)
{
item = this.FirstOrDefault();
TValue? value;
return _dictionary.TryRemove(item.Key, out value);
}
}
Time Sequence queue implementation (inherits above)
public class TimeSequenceQueue<T> : BlockingDictionary<DateTime, T>
{
private DateTime _previousTime;
private DateTime _nextTime;
private readonly int _intervalSeconds;
public TimeSequenceQueue(DateTime startTime, int intervalSeconds)
{
_intervalSeconds = intervalSeconds;
_previousTime = startTime;
_nextTime = startTime;
}
public override bool TryTake([MaybeNullWhen(false)] out KeyValuePair<DateTime, T> item)
{
item = _dictionary.SingleOrDefault(x => x.Key == _nextTime);
T? value = default(T);
if (item.Value == null)
return false;
bool result = _dictionary.TryRemove(item.Key, out value);
if (result)
{
_previousTime = _nextTime;
_nextTime = _nextTime.AddSeconds(_intervalSeconds);
}
return result;
}
}
Usage:
BlockingCollection<KeyValuePair<DateTime, object>> _queue = new BlockingCollection<KeyValuePair<DateTime, object>>(new TimeSequenceQueue<object>());
Consuming loop - started in new thread:
foreach (var item in _queue.GetConsumingEnumerable())
{
// feed downstream
}
When using the GetConsumingEnumerable() for loop, and the next key in the sequence is not present in the dictionary [...] I would like to wait for a specified amount of time and then attempt to take the item from the queue again.
I will try to answer this question generally, without paying too much attention to the specifics of your problem. So let's say that you are consuming
a BlockingCollection<T> like this:
foreach (var item in collection.GetConsumingEnumerable())
{
// Do something with the consumed item.
}
...and you want to avoid waiting indefinitely for an item to arrive. You want to wake up every 5 seconds and do something, before waiting/sleeping again.
Here is how you could do it:
while (!collection.IsCompleted)
{
bool consumed = collection.TryTake(out var item, TimeSpan.FromSeconds(5));
if (consumed)
{
// Do something with the consumed item.
}
else
{
// Do something before trying again to take an item.
}
}
The above pattern imitates the actual source code of the BlockingCollection<T>.GetConsumingEnumerable method.
If you want to get fancy you could incorporate this functionality in a custom extension method for the BlockingCollection<T> class, like this:
public static IEnumerable<(bool Consumed, T Item)> GetConsumingEnumerable<T>(
this BlockingCollection<T> source, TimeSpan timeout)
{
while (!source.IsCompleted)
{
bool consumed = source.TryTake(out var item, timeout);
yield return (consumed, item);
}
}
Usage example:
foreach (var (consumed, item) in collection.GetConsumingEnumerable(
TimeSpan.FromSeconds(5)))
{
// Do something depending on whether an item was consumed or not.
}

C# ThreadSafeSortedDictionary Implementation with limited methods

I have made an attempt at creating a ThreadSafeSortedDictionary, and I am working with .NET 4.0.
I have had a look at Thread safe SortedDictionary but not confident about the thread safety aspect.
Anyone think this can work, or NOT for a reason I cannot see associated with:
Thread safety - it needs to be thread safe
Performance/Efficiency - I am not majorly concerned about this (unless there is a huge problem with performance)
My code:
public class ThreadSafeSortedDict<TKey, TValue>
{
private readonly SortedDictionary<TKey, TValue> _dict;
private readonly ReaderWriterLockSlim _dictReaderWriterLockSlim;
private readonly ReaderWriterLockSlim _readonlyDictionaryLock;
public Dictionary<TKey, TValue> ReadOnly { get; set; }
public ThreadSafeSortedDict(IComparer<TKey> comparer)
{
_dict = new SortedDictionary<TKey, TValue>(comparer);
_dictReaderWriterLockSlim = new ReaderWriterLockSlim();
_readonlyDictionaryLock = new ReaderWriterLockSlim();
}
public void Add(TKey key, TValue value)
{
_dictReaderWriterLockSlim.EnterWriteLock();
try
{
_dict.Add(key,value);
}
finally
{
_dictReaderWriterLockSlim.ExitWriteLock();
}
SetReadOnlyDictionary();
}
public void AddRange(IEnumerable<KeyValuePair<TKey,TValue>> keyValues)
{
if (keyValues == null) return;
_dictReaderWriterLockSlim.EnterWriteLock();
try
{
foreach (var keyValue in keyValues)
{
Add(keyValue.Key, keyValue.Value);
}
}
finally
{
_dictReaderWriterLockSlim.ExitWriteLock();
}
SetReadOnlyDictionary();
}
public void Remove(TKey key)
{
_dictReaderWriterLockSlim.EnterWriteLock();
try
{
_dict.Remove(key);
}
finally
{
_dictReaderWriterLockSlim.ExitWriteLock();
}
SetReadOnlyDictionary();
}
public void Replace(IEnumerable<KeyValuePair<TKey, TValue>> newKeyValues)
{
if (newKeyValues == null) return;
_dictReaderWriterLockSlim.EnterWriteLock();
try
{
_dict.Clear();
AddRange(newKeyValues);
}
finally
{
_dictReaderWriterLockSlim.ExitWriteLock();
}
}
private void SetReadOnlyDictionary()
{
_readonlyDictionaryLock.EnterWriteLock();
try
{
ReadOnly = GetSortedKeyValues().ToDictionary(x => x.Key, x => x.Value);
}
finally
{
_readonlyDictionaryLock.ExitWriteLock();
}
}
private List<KeyValuePair<TKey, TValue>> GetSortedKeyValues()
{
_dictReaderWriterLockSlim.EnterReadLock();
try
{
return _dict.ToList();
}
finally
{
_dictReaderWriterLockSlim.ExitReadLock();
}
}
}
I don't see any obvious thread-safety issues with your code. To me, the bigger issues are matters of correctness:
A minor problem is that your ReadOnly property has a public setter. This means any code outside the class can set the property at any time, without any synchronization. Your class design should reflect the intended usage. Presumably you don't want any other code to change the property value, so the setter should be private.
A major problem is that while the class name is ThreadSafeSortedDict<TKey, TValue>, there's no publicly accessible view of the data that is sorted. When you set the ReadOnly property, you create a new unsorted dictionary from the original data. Even though you enumerate the source dictionary in order, which is sorted, there are no guarantees that the data in the new dictionary will remain in the order in which it was added. The dictionary class is, by design, an unordered collection.
If you really want to maintain the data in a SortedDictionary<TKey, TValue> object, and present a sorted view of it, you need to use some type of ordered collection as your ReadOnly property type. This could be another SortedDictionary<TKey, TValue>, but since the intent is for the collection to be read-only, IMHO it makes more sense to make the type IReadOnlyDictionary<TKey, TValue>, returning a ReadOnlyDictionary<TKey, TValue> object. And rather than creating a new object every time the sorted dictionary is modified, you can initialize it lazily and invalidate it when the sorted dictionary is modified.
For example:
public class ThreadSafeSortedDict<TKey, TValue>
{
private readonly SortedDictionary<TKey, TValue> _dict;
private IReadOnlyDictionary<TKey, TValue> _readOnly;
private readonly object _lock = new object();
public IReadOnlyDictionary<TKey, TValue> ReadOnly
{
get
{
lock (_lock)
{
if (_readOnly == null)
{
// NOTE: SortedList is faster when populating from already-sorted data
_readOnly = new ReadOnlyDictionary(new SortedList(_dict));
}
}
return _readOnly;
}
}
public ThreadSafeSortedDict(IComparer<TKey> comparer)
{
_dict = new SortedDictionary<TKey, TValue>(comparer);
}
public void Add(TKey key, TValue value)
{
lock (_lock)
{
_dict.Add(key,value);
_readOnly = null;
}
}
public void AddRange(IEnumerable<KeyValuePair<TKey,TValue>> keyValues)
{
if (keyValues == null) return;
lock (_lock)
{
foreach (var keyValue in keyValues)
{
Add(keyValue.Key, keyValue.Value);
}
_readOnly = null;
}
}
public void Remove(TKey key)
{
lock (_lock)
{
_dict.Remove(key);
_readOnly = null;
}
}
public void Replace(IEnumerable<KeyValuePair<TKey, TValue>> newKeyValues)
{
if (newKeyValues == null) return;
lock (_lock)
{
_dict.Clear();
AddRange(newKeyValues);
_readOnly = null;
}
}
}
Using lock instead of the reader-writer lock should not affect performance significantly, if at all, and makes the code a lot more obviously thread-safe. It seems to me that the above should be sufficient. Still, if it's not, some improvements you could make include:
Going ahead and using the reader-writer lock. You'll need to acquire the reader lock in the ReadOnly property, and upgrade it to a writer lock if you need to set the _readOnly field (i.e. if it's set to null). This could improve performance if you have a very large amount of contention on the ReadOnly property.
Not bothering with the custom type, and using ConcurrentDictionary<TKey, TValue> instead, just copying its contents and sorting them after the fact when you need a sorted view of the dictionary. Whether this is useful in your case I can't say, as there's not enough context in the question. If you will still need to implement some type of synchronization to avoid trying to retrieve the sorted view when some other thread is trying to modify the collection, then you'll probably want some kind of wrapper type anyway.
Having your type implement IReadOnlyDictionary<TKey, TValue> rather than having a ReadOnly property. Then you can pass your object as a read-only dictionary object where needed, but code that needs to actually modify the dictionary can still do so via the original type.
Given what information is available in the question at the moment, I'd say option #3 is probably best. It would require the most work, but seems to me to provide the cleanest, most efficient API. You would avoid having to have the ReadOnly property and you would avoid having to make a copy of the collection. The class itself would serve as the read-only copy, and it would automatically be kept up-to-date with respect to the underlying dictionary collection, because the implementation of IReadOnlyDictionary<TKey, TValue> would just defer to the underlying dictionary (with synchronization, of course).
First of all, thanks to #Peter Duniho for the information and answer.
I first attempted using reader writer locks, but had quite a bit of trouble and took the advise to use locks for simplicity.
My solution is modifying Peters answer, because I did not want to lock everytime the ReadOnly dictionary was accessed.
And my ultimate goals for this class are:
At any point in time, I would like to access the ReadOnly collection (and stop users to write to it).
If something in the underlying dictionary gets added/removed, I do not want this to affect other's who are already using/reading/iterating the ReadOnly they have access to.
Here is the solution:
public class ThreadSafeSortedDictionary<TKey, TValue>
{
private readonly SortedDictionary<TKey, TValue> _dict;
private readonly object _sync;
public ReadOnlyDictionary<TKey, TValue> ReadOnly { get; private set; }
public IEnumerable<TValue> Values
{
get { return ReadOnly.Values; }
}
public IEnumerable<TKey> Keys
{
get { return ReadOnly.Keys; }
}
public int Count
{
get { return ReadOnly.Count; }
}
public ThreadSafeSortedDictionary(IComparer<TKey> comparer)
{
_dict = new SortedDictionary<TKey, TValue>(comparer);
_sync = new object();
}
public void Add(TKey key, TValue value)
{
lock (_sync)
{
_dict.Add(key, value);
SetReadOnlyDictionary();
}
}
public void AddRange(IEnumerable<KeyValuePair<TKey, TValue>> keyValues)
{
if (keyValues == null) return;
InternalAddRange(keyValues);
}
public void Remove(TKey key)
{
lock (_sync)
{
_dict.Remove(key);
SetReadOnlyDictionary();
}
}
public void Replace(IEnumerable<KeyValuePair<TKey, TValue>> newKeyValues)
{
//Throw exception, because otherwise we'll end up clearing the dictionary
if (newKeyValues == null) throw new ArgumentNullException("newKeyValues");
InternalAddRange(newKeyValues, true);
}
private void InternalAddRange(IEnumerable<KeyValuePair<TKey, TValue>> keyValues, bool clearBeforeAdding = false)
{
lock (_sync)
{
if (clearBeforeAdding) _dict.Clear();
foreach (var kvp in keyValues)
{
_dict.Add(kvp.Key, kvp.Value);
}
SetReadOnlyDictionary();
}
}
private void SetReadOnlyDictionary()
{
lock (_sync)
{
ReadOnly = new DictionaryReadOnly<TKey, TValue>(new SortedList<TKey, TValue>(_dict, _dict.Comparer));
}
}
}
And I used the ReadOnlyDictionary from the answer of Is there a read-only generic dictionary available in .NET?
I used SortedList vs SortedDictionary for the ReadOnly property, because I am creating them quite a few times (during add/remove/replace), and because
1. SortedList perform better in memory
2. are quicker in lookup, and as I am only using them for Read Only purposes, only lookup will be needed.

Prioritized queues in Task Parallel Library

Is there any prior work of adding tasks to the TPL runtime with a varying priority?
If not, generally speaking, how would I implement this?
Ideally I plan on using the producer-consumer pattern to add "todo" work to the TPL. There may be times where I discover that a low priority job needs to be upgraded to a high priority job (relative to the others).
If anyone has some search keywords I should use when searching for this, please mention them, since I haven't yet found code that will do what I need.
So here is a rather naive concurrent implementation around a rather naive priority queue. The idea here is that there is a sorted set that holds onto pairs of both the real item and a priority, but is given a comparer that just compares the priority. The constructor takes a function that computes the priority for a given object.
As for actual implementation, they're not efficiently implemented, I just lock around everything. Creating more efficient implementations would prevent the use of SortedSet as a priority queue, and re-implementing one of those that can be effectively accessed concurrently is not going to be that easy.
In order to change the priority of an item you'll need to remove the item from the set and then add it again, and to find it without iterating the whole set you'd need to know the old priority as well as the new priority.
public class ConcurrentPriorityQueue<T> : IProducerConsumerCollection<T>
{
private object key = new object();
private SortedSet<Tuple<T, int>> set;
private Func<T, int> prioritySelector;
public ConcurrentPriorityQueue(Func<T, int> prioritySelector, IComparer<T> comparer = null)
{
this.prioritySelector = prioritySelector;
set = new SortedSet<Tuple<T, int>>(
new MyComparer<T>(comparer ?? Comparer<T>.Default));
}
private class MyComparer<T> : IComparer<Tuple<T, int>>
{
private IComparer<T> comparer;
public MyComparer(IComparer<T> comparer)
{
this.comparer = comparer;
}
public int Compare(Tuple<T, int> first, Tuple<T, int> second)
{
var returnValue = first.Item2.CompareTo(second.Item2);
if (returnValue == 0)
returnValue = comparer.Compare(first.Item1, second.Item1);
return returnValue;
}
}
public bool TryAdd(T item)
{
lock (key)
{
return set.Add(Tuple.Create(item, prioritySelector(item)));
}
}
public bool TryTake(out T item)
{
lock (key)
{
if (set.Count > 0)
{
var first = set.First();
item = first.Item1;
return set.Remove(first);
}
else
{
item = default(T);
return false;
}
}
}
public bool ChangePriority(T item, int oldPriority, int newPriority)
{
lock (key)
{
if (set.Remove(Tuple.Create(item, oldPriority)))
{
return set.Add(Tuple.Create(item, newPriority));
}
else
return false;
}
}
public bool ChangePriority(T item)
{
lock (key)
{
var result = set.FirstOrDefault(pair => object.Equals(pair.Item1, item));
if (object.Equals(result.Item1, item))
{
return ChangePriority(item, result.Item2, prioritySelector(item));
}
else
{
return false;
}
}
}
public void CopyTo(T[] array, int index)
{
lock (key)
{
foreach (var item in set.Select(pair => pair.Item1))
{
array[index++] = item;
}
}
}
public T[] ToArray()
{
lock (key)
{
return set.Select(pair => pair.Item1).ToArray();
}
}
public IEnumerator<T> GetEnumerator()
{
return ToArray().AsEnumerable().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void CopyTo(Array array, int index)
{
lock (key)
{
foreach (var item in set.Select(pair => pair.Item1))
{
array.SetValue(item, index++);
}
}
}
public int Count
{
get { lock (key) { return set.Count; } }
}
public bool IsSynchronized
{
get { return true; }
}
public object SyncRoot
{
get { return key; }
}
}
Once you have an IProducerConsumerCollection<T> instance, which the above object is, you can use it as the internal backing object of a BlockingCollection<T> in order to have an easier to use user interface.
ParallelExtensionsExtras contains several custom TaskSchedulers that could be helpful either directly or as a base for your own scheduler.
Specifically, there are two schedulers that may be interesting for you:
QueuedTaskScheduler, which allows you to schedule Tasks at different priorities, but doesn't allow changing the priority of enqueued Tasks.
ReprioritizableTaskScheduler, which doesn't have different priorities, but allows you to move a specific Task to the front or to the back of the queue. (Though changing priority is O(n) in the number of currently waiting Tasks, which could be a problem if you had many Tasks at the same time.)

What should I use as an alternative to System.Collections.Concurrent.ConcurrentDictionary (3.5)

I require a thread safe collection but are unable to use ConcurrentDictionary as its .NET 4.0, and I need to use .NET 3.5. What are alternatives are out there?
In the end I found what I was after. A nuget package of the TPL backported to 3.5. It can be found here
http://nuget.org/packages/TaskParallelLibrary
Have a look at this article
“Thread safe” Dictionary(TKey,TValue)
For Framework 3.5 I see the following options:
Dictionary + Monitor. Simple wrap to lock
Dictionary + ReaderWriterLock. Better than the previous one, because has got read and write locks. So several threads can read, and just one - write.
Dictionary + ReaderWriterLockSlim. It's just optimisation of the previous one.
Hashtable. From my experience, it's the slowest method. Check Hashtable.Synchronized() method, it's a ready to go solution from Microsoft.
I would use Dictionary + ReaderWriterLock or ReaderWriterLockSlim.
You can create a simple wrapper and only implement what you need. Keep in mind that it is not thread safe to perform an operation on a Dictionary that relies on the result of a previous call. This is exemplified in the TryAdd method.
class ConcurrentMap<K, V>
{
readonly Dictionary<K, V> _map = new Dictionary<K, V>();
public bool TryGetValue(K key, out V value)
{
lock (_map)
{
return _map.TryGetValue(key, out value);
}
}
public bool TryAdd(K key, V value)
{
lock (_map)
{
if (!_map.ContainsKey(key))
{
_map.Add(key, value);
return true;
}
return false;
}
}
public bool TryRemove(K key)
{
lock (_map)
{
return _map.Remove(key);
}
}
}
I just needed that for .Net 3.5... someone's still there back :)
adding my class, added some more functionality than previous attached codes.
public class SafeDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
{
private readonly object _Padlock = new object();
private readonly Dictionary<TKey, TValue> _Dictionary = new Dictionary<TKey, TValue>();
public TValue this[TKey key]
{
get
{
lock (_Padlock)
{
return _Dictionary[key];
}
}
set
{
lock (_Padlock)
{
_Dictionary[key] = value;
}
}
}
public bool TryGetValue(TKey key, out TValue value)
{
lock (_Padlock)
return _Dictionary.TryGetValue(key, out value);
}
public bool TryAdd(TKey key, TValue value)
{
lock (_Padlock)
{
if (!_Dictionary.ContainsKey(key))
{
_Dictionary.Add(key, value);
return true;
}
return false;
}
}
public bool TryRemove(TKey key)
{
lock (_Padlock)
{
return _dictionary.Remove(key);
}
}
}
internal void Add(TKey key, TValue val)
{
lock (_Padlock)
{
_Dictionary.Add(key, val);
}
}
public bool ContainsKey(TKey id)
{
lock (_Padlock)
return _Dictionary.ContainsKey(id);
}
public List<KeyValuePair<TKey, TValue>> OrderBy(Func<KeyValuePair<TKey, TValue>, TKey> func)
{
lock (_Padlock)
return _Dictionary.OrderBy(func).ToList();
}
public Dictionary<TKey, TValue>.ValueCollection Values
{
get
{
lock (_Padlock)
return _Dictionary.Values;
}
}
public Dictionary<TKey, TValue>.KeyCollection Keys
{
get
{
lock (_Padlock)
return _Dictionary.Keys;
}
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
lock (_Padlock)
return _Dictionary.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
lock (_Padlock)
return _Dictionary.GetEnumerator();
}
}

Concurrent collections and unique elements

I have a concurrent BlockingCollection with repeated elements. How can modify it to add or get distinct elements?
The default backing store for BlockingCollection is a ConcurrentQueue. As somebody else pointed out, it's rather difficult to add distinct items using that.
However, you could create your own collection type that implements IProducerConsumerCollection, and pass that to the BlockingCollection constructor.
Imagine a ConcurrentDictionary that contains the keys of the items that are currently in the queue. To add an item, you call TryAdd on the dictionary first, and if the item isn't in the dictionary you add it, and also add it to the queue. Take (and TryTake) get the next item from the queue, remove it from the dictionary, and return.
I'd prefer if there was a concurrent HashTable, but since there isn't one, you'll have to do with ConcurrentDictionary.
Here is an implementation of a IProducerConsumerCollection<T> collection with the behavior of a queue, that also rejects duplicate items:
public class ConcurrentQueueNoDuplicates<T> : IProducerConsumerCollection<T>
{
private readonly Queue<T> _queue = new();
private readonly HashSet<T> _set;
private object Locker => _queue;
public ConcurrentQueueNoDuplicates(IEqualityComparer<T> comparer = default)
{
_set = new(comparer);
}
public bool TryAdd(T item)
{
lock (Locker)
{
if (!_set.Add(item))
throw new DuplicateKeyException();
_queue.Enqueue(item); return true;
}
}
public bool TryTake(out T item)
{
lock (Locker)
{
if (_queue.Count == 0)
throw new InvalidOperationException();
item = _queue.Dequeue();
bool removed = _set.Remove(item);
Debug.Assert(removed);
return true;
}
}
public int Count { get { lock (Locker) return _queue.Count; } }
public bool IsSynchronized => false;
public object SyncRoot => throw new NotSupportedException();
public T[] ToArray() { lock (Locker) return _queue.ToArray(); }
public IEnumerator<T> GetEnumerator() => ToArray().AsEnumerable().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void CopyTo(T[] array, int index) => throw new NotSupportedException();
public void CopyTo(Array array, int index) => throw new NotSupportedException();
}
public class DuplicateKeyException : InvalidOperationException { }
Usage example:
BlockingCollection<Item> queue = new(new ConcurrentQueueNoDuplicates<Item>());
//...
try { queue.Add(item); }
catch (DuplicateKeyException) { Console.WriteLine($"The {item} was rejected."); }
Caution: Calling queue.TryAdd(item); is not having the expected behavior of returning false if the item is a duplicate. Any attempt to add a duplicate item results invariably in a DuplicateKeyException. Do not attempt to "fix" the above ConcurrentQueueNoDuplicates<T>.TryAdd implementation, or the TryTake, by returning false. The BlockingCollection<T> will react by throwing a different exception (InvalidOperationException), and on top of that its internal state will become corrupted. There is currently (.NET 7) a bug that reduces by one the effective capacity of a BlockingCollection<T> whose underlying storage has a TryAdd implementation that returns false. The bug has been fixed for .NET 8, which will prevent the corruption, but it won't change the error-throwing behavior.

Categories

Resources