BlueRaja / High-Speed-Priority-Queue-for-C-Sharp

A C# priority queue optimized for pathfinding applications
MIT License
1.15k stars 169 forks source link

Consuming SimplePriorityQueue in multi threaded context #21

Closed s3057043 closed 7 years ago

s3057043 commented 7 years ago

Background:

I have been evaluating this priority queue to manage the processing of a large collection of documents that my program will receive. These come from 2 sources:

  1. from a web service API call from automation
  2. from a user in another program

At the head of the queue, I have a set of "processors" that operate upon the document (the number of threads are based on the desired server load / processing velocity tradeoff user wants).

I would like the user (point 2 above) to be able to "push in" with their document. My problem though is that the web service automation may have submitted details that are a pre-requisite processing to the user's document, so I have occasions where I need to escalate the priority of already queued items.

On the surface this is pretty straight forward with SimplePriorityQueue

Have a dictionary of Item by id and call UpdatePriority on it first before enqueuing the user submitted document. (I have other mechanisms in place to ensure that two documents for the same id don't get allocated to different "processors"

Issue: Unless I have missed something, there is no way that I can see for a consumer that dequeues to know whether it is safe to do so. In a multi threaded environment, even if I check count > 0, another thread may call Dequeue before me. Similarly, if I want to call UpdatePriority, there is a race condition between me calling Contains, receiving true and then UpdatePriority because _queue is unlocked (briefly).

Is it possible to use the Try... convention (like ConcurrentDictionary etc)

For example: public bool TryUpdatePriority(TItem item, TPriority priority) public bool TryRemove(TItem item) public bool TryDequeue(out TItem item)

It feels a bit dirty (and inefficient) to be capturing and swallowing InvalidOperationExceptions

BlueRaja commented 7 years ago

I threw in the "multi-threaded support" rather haphazardly, simply locking in the places that need to be locked. I should have put more thought into it because you're absolutely right, there's no way to use it usefully without those methods.

I'll put these in for the next release, but it might be a while because I'm extremely busy with several other projects. If you'd like to put together a PR, that would speed things up significantly.

Thanks!

BlueRaja commented 7 years ago

Completed in v4.1.1.

SimplePriorityQueue now has the following methods:

void EnqueueWithoutDuplicates(TItem, TPriority)
bool TryFirst(out TItem)
bool TryDequeue(out TItem)
bool TryRemove(TItem)
bool TryUpdatePriority(TItem, TPriority)
bool TryGetPriority(TItem, out TPriority)