This is a simple class, that allows you to queue up Actions for processing them in a single, dedicated thread.
Once the queue is empty, the thread terminates.
When new items are queued up after the thread terminated, a new one is created.
What is this for?
Not sure if you have that situation actually. So maybe nothing?
Basically, I had some logic that needed to do some work at irregular intervals.
Now you’re thinking “go for Task or ThreadPool“.
Both good solutions.
But the things I needed them to perform were resource hogs (well, still are), mostly in terms of processing time.
That’s why I wanted a dedicated thread, ideally one for which I could set a higher priority.
The downside of that is of course to either have a thread constantly running idle or the overhead of creating new threads all the time.
Luckily the stuff the thread was meant for usually came in bulk, so a long time of nothing followed by a quick burst of a handful of actions. Which in turn meant I could just keep the thread running for a bunch of actions, saving some overhead, but terminate it until the next batch came in, preventing idle threads.
In the end this system is a compromise between the two options. Not perfect, not optimal, but good enough for me.
And maybe for you? The basic premise isn’t exactly uncommon, and it’s one of those things you spend less than ten minutes and a few lines of code on when you actually need it. This is just a more formal implementation for reuse.
The code should be pretty self explanatory.
Admittedly, this is a bit over designed for what it is…
Again, it’s one of those things you just write yourself when needed, with only what you actually need. And usually this thing is mixed into something else, rather than having its own class. At least that’s how I see it.
/// <summary> /// Processes a queue of actions on a dedicated thread that stays alive until the queue is finished /// </summary> public class QueuedThreadInvoker { private readonly object _lock; private readonly Queue<Action> _queue; private readonly string _name; private readonly ThreadPriority _priority; private readonly ApartmentState _apartmentState; private readonly bool _isBackground; private Thread _thread; /// <summary> /// Creates a new <see cref="QueuedThreadInvoker"/> for queueing actions on a custom thread /// </summary> /// <param name="name">The name of the thread</param> /// <param name="priority">The priority of the thread</param> /// <param name="apartmentState">THe apartment state of the thread</param> /// <param name="isBackground">Whether to run the thread in the back- or foreground</param> public QueuedThreadInvoker(string name = null, ThreadPriority priority = ThreadPriority.Normal, ApartmentState apartmentState = ApartmentState.MTA, bool isBackground = true) { _name = name ?? nameof(QueuedThreadInvoker); _priority = priority; _apartmentState = apartmentState; _isBackground = isBackground; _lock = new object(); _queue = new Queue<Action>(); _thread = null; } /// <summary> /// Triggered from the worker thread when an action throws an exception /// </summary> public event Action<Exception> UnhandledException; /// <summary> /// Adds an action to the queue. If no thread is active, a new thread is created for processing it. /// </summary> /// <param name="action">The action to queue up</param> /// <returns>True if a new thread was created, false if the action was added to an active queue</returns> /// <exception cref="NullReferenceException"><paramref name="action"/> is null</exception> public bool Invoke(Action action) { if (action is null) throw new ArgumentNullException(nameof(action)); lock (_lock) { _queue.Enqueue(action); if (!(_thread is null)) return false; _thread = new Thread(ProcessQueue) { Name = _name, Priority = _priority }; _thread.SetApartmentState(_apartmentState); _thread.IsBackground = _isBackground; _thread.Start(); } return true; } /// <summary> /// Blocks the current thread until the active queue is completed /// </summary> public void WaitForQueueToFinish() { Thread thread; lock (_lock) thread = _thread; thread?.Join(); } private void ProcessQueue() { bool itemsInQueue; Action action = null; lock (_lock) { itemsInQueue = _queue.Count > 0; if (itemsInQueue) action = _queue.Dequeue(); else _thread = null; } while (itemsInQueue) { try { action.Invoke(); } catch (Exception e) { try { UnhandledException?.Invoke(e); } catch { // ignored } } lock (_lock) { itemsInQueue = _queue.Count > 0; if (itemsInQueue) action = _queue.Dequeue(); else _thread = null; } } } }
