Producer/Consumer Thread Pool w/ Main Thread Support - Infrequent Deadlock? - c#

I have a C# thread pool class that is based heavily on the producer/consumer code from https://stackoverflow.com/a/1656662/782181. NOTE: I'm doing this instead of using BlockingCollection because I'm stuck with .NET2.0!
I added a function to the class that can be called from the main thread to allow the main thread to do some work. My thinking here was that, at some point, the main thread waits for work to be done, but instead of waiting, I could also have the main thread do some of the work to speed things up.
Here's a slimmed version of the class to demonstrate:
public static class SGThreadPool
{
// Shared object to lock access to the queue between threads.
private static object locker = new object();
// The various threads that are doing our work.
private static List<Thread> workers = null;
// A queue of tasks to be completed by the workers.
private static Queue<object> taskQueue = new Queue<object>();
private static Queue<WaitCallback> taskCallbacks = new Queue<WaitCallback>();
//OMMITTED: Init function (starts threads)
// Enqueues a task for a thread to do.
public static void EnqueueTask(WaitCallback callback, object context)
{
lock(locker)
{
taskQueue.Enqueue(context);
taskCallbacks.Enqueue(callback);
Monitor.PulseAll(locker); //Q: should I just use 'Pulse' here?
}
}
// Can be called from main thread to have it "help out" with tasks.
public static bool PerformTask()
{
WaitCallback taskCallback = null;
object task = null;
lock(locker)
{
if(taskQueue.Count > 0)
{
task = taskQueue.Dequeue();
}
if(taskCallbacks.Count > 0)
{
taskCallback = taskCallbacks.Dequeue();
}
}
// No task means no work, return false.
if(task == null || taskCallback == null) { return false; }
// Do the work!
taskCallback(task);
return true;
}
private static void Consume()
{
while(true)
{
WaitCallback taskCallback = null;
object task = null;
lock(locker)
{
// While no tasks in the queue, wait.
while(taskQueue.Count == 0)
{
Monitor.Wait(locker);
}
// Get a task.
task = taskQueue.Dequeue();
taskCallback = taskCallbacks.Dequeue();
}
// Null task signals an exit.
if(task == null || taskCallback == null) { return; }
// Call consume callback with task as context.
taskCallback(task);
}
}
}
Basically, I can enqueue a number of tasks to be performed by background threads. But it is also possible for the main thread to take a task and perform it by calling PerformTask().
I'm running into an infrequent problem where the main thread will try to "lock" in PerformTask(), but the lock is already taken. The main thread waits, but the lock doesn't ever become available, for some reason.
Nothing in the code is jumping out at me as a problem causing the deadlock - I'm hoping that someone else might be able to spot the problem. I've been looking at this for a couple hours, and I'm not sure why the main thread would get stuck at the "lock()" call in PerformTask(). It seems like no other thread would be holding the lock indefinitely? Is it a bad idea to allow the main thread to interact with the pool in this way?

Hmm, so, while I would still like to know why the code above could deadlock in certain scenarios, I think I've found a workaround that will do the trick.
If the main thread is going to be doing work here, I want to make sure the main thread doesn't get blocked for a long period of time. After all, a general dev rule: don't block the main thread!
So, the solution I'm trying is to use Monitor.TryEnter directly, rather than using lock() for the main thread. This allows me to specify a timeout for how long the main thread is willing to wait for the lock.
public static bool PerformTask()
{
WaitCallback taskCallback = null;
object task = null;
// Use TryEnter, rather than "lock" because
// it allows us to specify a timeout as a failsafe.
if(Monitor.TryEnter(locker, 500))
{
try
{
// Pull a task from the queue.
if(taskQueue.Count > 0)
{
task = taskQueue.Dequeue();
}
if(taskCallbacks.Count > 0)
{
taskCallback = taskCallbacks.Dequeue();
}
}
finally
{
Monitor.Exit(locker);
}
}
// No task means no work, return false.
if(task == null || taskCallback == null) { return false; }
// Do the work!
taskCallback(task);
return true;
}
In this code, the thread will wait to acquire the lock for up to 500ms. If it can't for whatever reason, it fails to do any tasks, but at least it doesn't get stuck. It seems like a good idea to not put the main thread in a position where it could wait indefinitely.
I believe that when you use lock(), the compiler generates similar code anyways, so I don't think there would be any performance issue with this solution.

Related

How to stop task(thread) and release in other task? [duplicate]

Hi guys I start threads with such code:
Thread[] thr;
private void button1_Click(object sender, EventArgs e)
{
decimal value = numericUpDown2.Value;
int i = 0;
threads_count = (int)(value);
thr = new Thread[threads_count];
for (; i < threads_count; i++)
{
thr[i] = new Thread(new ThreadStart(go));
thr[i].IsBackground = true;
thr[i].Start();
}
}
How to stop all them if my condition become true
A number of the answers say to abort the thread. Never abort a thread unless it is an emergency situation and you are shutting down the application.
The CLR guarantees that its internal data structures are not corrupted by a thread abort. This is the only (*) guarantee made by the CLR with respect to thread aborts. It specifically does not guarantee:
That the thread actually will abort. Threads can harden themselves against being terminated.
That any data structure that is not in the CLR itself will be uncorrupted. Thread aborts in the middle of crucial operations can leave BCL data structures or user data structures in arbitrarily inconsistent states. This can crash your process mysteriously later.
That locks will be released. Aborting threads can cause locks to be held forever, it can cause deadlocks, and so on.
In case I am not being clear: it is insanely dangerous to abort a thread and you should only do so when all the alternatives are worse.
So what if you want to start up a thread and then shut it down cleanly?
First, don't do that. Don't start a thread in the first place. Start a Task<T> with a cancellation token and when you want to shut it down, signal its cancellation token.
If you do have to start a thread, then start the thread such that there is some mechanism whereby the main thread and the working thread can cleanly and safely communicate "I want you to shut yourself down cleanly at this time".
If you don't know how to do that then stop writing multithreaded code until you learn how to do that.
(*) This is a small lie; the CLR also makes certain guarantees with respect to the interactions of thread aborts and special code regions such as constrained execution regions and finally blocks.
You can use a CancellationToken to signal when the operation should stop.
Create a CancellationTokenSource as an instance field of your type that you initialize in the button click handler.
In your background method periodically check the IsCancellationRequested property of the Token in the token source, or call ThrowIfCancellationRequested() if you want it to just throw an exception if it is canceled.
When you want to stop the threads call Cancel on the token source.
Brutal way (not recommended) - use Thread.Abort method to abort threads. This method raises ThreadAbortException on thread. Like this:
foreach(Thread thread in thr)
thread.Abort();
But better way is notifying thread about cancellation and letting it correctly finish its job. You can do it simply with .Net 4 tasks:
Task[] thr = new Task[threads_count];
var source = new CancellationTokenSource();
for (int i = 0; i < threads_count; i++)
{
thr[i] = Task.Factory.StartNew(go, source.Token);
}
// later, when condition is met
source.Cancel();
And here is how cancellation should look like:
private static void go(object obj)
{
CancellationToken token = (CancellationToken)obj;
while (true)
{
if (token.IsCancellationRequested)
return;
// do some work
}
}
If you want to know how to terminate the thread gracefully, I'd recommend you to take a look the following example on MSDN:
using System;
using System.Threading;
public class Worker
{
public void DoWork()
{
while (!_shouldStop)
{
Console.WriteLine("worker thread: working...");
}
Console.WriteLine("worker thread: terminating gracefully.");
}
public void RequestStop()
{
_shouldStop = true;
}
// Volatile is used as hint to the compiler that this data
// member will be accessed by multiple threads.
private volatile bool _shouldStop;
}
public class WorkerThreadExample
{
static void Main()
{
Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork);
workerThread.Start();
Console.WriteLine("main thread: Starting worker thread...");
while (!workerThread.IsAlive); // Loop until worker thread activates
// Put the main thread to sleep for 1 millisecond to
// allow the worker thread to do some work:
Thread.Sleep(1);
workerObject.RequestStop();
// Use the Join method to block the current thread
// until the object's thread terminates.
workerThread.Join();
Console.WriteLine("main thread: Worker thread has terminated.");
}
}
This is Windows Form Code in which:
1) On Clicking start button, Main Thread creates another Thread
2) Again created Thread creates on more Thread.
3) On clicking Stop button, First the last Thread should terminate Then the Thread created by Main thread should Terminate.
namespace Thread_TerminateProblem
{
public partial class Form1 : Form
{
private static AutoResetEvent m_ResetEvent = null;
private static ManualResetEvent m_ResetEvent_Thread = new ManualResetEvent(false);
enum ServiceState { Start, Stop };
bool flag = false;
int x = 0;
ServiceState _state;
public Form1()
{
InitializeComponent();
}
private void btnStart_Click(object sender, EventArgs e)
{
flag = true;
_state = ServiceState.Start;
m_ResetEvent = new AutoResetEvent(true);
Thread t1 = new Thread(fun_Thread1);
t1.Start();
t1.Name = "Thread1";
}
private void btnStop_Click(object sender, EventArgs e)
{
_state = ServiceState.Stop;
m_ResetEvent.Set();
}
private void fun_Thread1()
{
while (true)
{
m_ResetEvent.WaitOne();
switch (_state)
{
case ServiceState.Start:
{
Thread t = new Thread(fun_Thread2);
t.Start();
t.Name = "Thread2";
break;
}
case ServiceState.Stop:
{
m_ResetEvent_Thread.Set();
flag = true;
break;
}
}
// When the child Thread terminates, Then only this thread should terminate
if (flag == true)
{
// Waiting for notification from child Thread
notifyParent.WaitOne();
Thread.Sleep(100);
break;
}
m_ResetEvent.Reset();
}
}
private static ManualResetEvent notifyParent = new ManualResetEvent(false);
private void fun_Thread2()
{
while (true)
{
if (m_ResetEvent_Thread.WaitOne(1, false))
{
notifyParent.Set();
break;
}
x++;
}
}
}
}
simplistic answer is to use the thread Abort() method however your code does not really make it clear what condition,
what loop tests vs a condition? why do you need to abort a thread? I am asking as there may be a better way to approach this

Scheduling a method to run rather than lock in C#

I have a method (let's call it "CheckAll") that is called from multiple areas of my program, and can therefore be called for a 2nd time before the 1st time has completed.
To get around this I have implemented a "lock" that (if I understand it correctly), halts the 2nd thread until the 1st thread has completed.
However what I really want is for this 2nd call to return to the calling method immediately (rather than halt the thread), and to schedule CheckAll to be run again once it has completed the 1st time.
I could setup a timer to do this but that seems cumbersome and difficult. Is there a better way?
Easy/cheap implementation.
private Thread checkThread = null;
private int requests = 0;
void CheckAll()
{
lock(SyncRoot){
if (checkThread != null; && checkThread.ThreadState == ThreadState.Running)
{
requests++;
return;
}else
{
CheckAllImpl();
}
}
}
void CheckAppImpl()
{
// start a new thread and run the following code in it.
checkThread = new Thread(newThreadStart( () => {
while (true)
{
// 1. Do what ever checkall need to do.
// 2.
lock (SyncRoot)
{
requests--;
if (!(requests > 0))
break;
}
}});
checkThread.Start();
}
Just on a side note, this can have some race conditions. Better implementation swould be to use ConcurrentQueue introduced in .NET 4 which handles all the threading craziness for you.
UPDATE: Here's a more 'cool' implementation using ConcurrentQueue (turns out we don't need TPL.)
public class CheckAllService
{
// Make sure you don't create multiple
// instances of this class. Make it a singleton.
// Holds all the pending requests
private ConcurrentQueue<object> requests = new ConcurrentQueue<object>();
private object syncLock = new object();
private Thread checkAllThread;
/// <summary>
/// Requests to Check All. This request is async,
/// and will be serviced when all pending requests
/// are serviced (if any).
/// </summary>
public void RequestCheckAll()
{
requests.Enqueue("Process this Scotty...");
lock (syncLock)
{ // Lock is to make sure we don't create multiple threads.
if (checkAllThread == null ||
checkAllThread.ThreadState != ThreadState.Running)
{
checkAllThread = new Thread(new ThreadStart(ListenAndProcessRequests));
checkAllThread.Start();
}
}
}
private void ListenAndProcessRequests()
{
while (requests.Count != 0)
{
object thisRequestData;
requests.TryDequeue(out thisRequestData);
try
{
CheckAllImpl();
}
catch (Exception ex)
{
// TODO: Log error ?
// Can't afford to fail.
// Failing the thread will cause all
// waiting requests to delay until another
// request come in.
}
}
}
protected void CheckAllImpl()
{
throw new NotImplementedException("Check all is not gonna write it-self...");
// TODO: Check All
}
}
NOTE: I use a real Thread instead of a TPL Task because a Task doesn't hold on to a real thread as an optimization. When there's no Thread, that means at the time your application closes, any waiting CheckAll requests are ignored.(I got bitten hard by this when I thought I'm so smart to call my logging methods in a task once, which ignored a couple of dozen log records when closing. CLR checks and waits for any waiting threads when gracefully exiting.)
Happy Coding...
Use a separate thread to call CheckAll() in a loop that also waits on a semaphore. A 'PerformCheck()' method signals the semaphore.
Your system can then make as many calls to 'PerformCheck()' as it might wish, from any thread, and CheckAll() will be run exactly as many times as there are PerformCheck() calls, but with no blocking on PerformCheck().
No flags, no limits, no locking, no polling.
You can setup a flag for this.
When this CheckAll() method runs. at the end of this method you can put a flag for each of the separate method. means if the method is being called from other method lets say a() then immidiately after this it is going to be called from b() then>>> when it is called from a() put a flaga variable(which may be global) in CheckAll() at the end(assign it to particular value) and give the condition in b() according to the flaga variable value. Means something like this...
public a()
{
CheckAll();
}
public b()
{
.
.
(put condition here for check when flaga=1 from the method CheckAll())
CheckAll();
}
public CheckAll()
{
.
.
.
flaga=1;
}
}

Threading only block the first thread (Attempt Two)

I have asked this question before - but I have spent some time thinking about it and have implemented a working version.
Overview
1) Threads are being created to perform a certain task.
2) Only one thread can perform the task at a time.
3) Each thread performs the exact same task. (Does a bunch of checks and validations on a system)
3) The threads are being created faster than the task can be performed. (I have no control over the thread creation)
Result is that overtime I get a backlog of threads to perform the task.
What I have implemented goes as follows
1) Thread checks to see how many active threads there are.
2) If there are 0 threads it is marked to PerformTask and it starts the task
3) If there is 1 thread it is marked to PerformTak and it blocks
4) If there is more than 1 thread the thread is not marked to PerformTasks and just dies
The idea is that if there is a thread waiting to perform the task already I just kill the thread.
Here is the code that I came up with
bool tvPerformTask = false;
ivNumberOfProcessesSemaphore.WaitOne();
if (ivNumberOfProcessesWaiting == 0 ||
ivNumberOfProcessesWaiting == 1)
{
ivNumberOfProcessesWaiting++;
tvPerformTask = true;
}
ivNumberOfProcessesSemaphore.Release();
if (tvPerformTask)
{
//Here we perform the work
ivProcessSemaphore.WaitOne();
//Thread save
ivProcessSemaphore.Release();
ivNumberOfProcessesSemaphore.WaitOne();
ivNumberOfProcessesWaiting--;
ivNumberOfProcessesSemaphore.Release();
}
else
{
//we just let the thread die
}
The problem that I have is not that it doesn't work it is just that I do not find the code elegant specifically I am not very happy that I need 2 semaphores an integer and a local flag to control it all. If there a way to implement this or pattern that would make the code simpler.
How about this?
private readonly _lock = new object();
private readonly _semaphore = new Semaphore(2, 2);
private void DoWork()
{
if (_semaphore.WaitOne(0))
{
try
{
lock (_lock)
{
// ...
}
}
finally
{
_semaphore.Release();
}
}
}
Consider using a ThreadPool instead of trying to managing the creation and destruction of individual threads on your own.

what's wrong with my producer-consumer queue design?

I'm starting with the C# code example here. I'm trying to adapt it for a couple reasons: 1) in my scenario, all tasks will be put in the queue up-front before consumers will start, and 2) I wanted to abstract the worker into a separate class instead of having raw Thread members within the WorkerQueue class.
My queue doesn't seem to dispose of itself though, it just hangs, and when I break in Visual Studio it's stuck on the _th.Join() line for WorkerThread #1. Also, is there a better way to organize this? Something about exposing the WaitOne() and Join() methods seems wrong, but I couldn't think of an appropriate way to let the WorkerThread interact with the queue.
Also, an aside - if I call q.Start(#) at the top of the using block, only some of the threads every kick in (e.g. threads 1, 2, and 8 process every task). Why is this? Is it a race condition of some sort, or am I doing something wrong?
using System;
using System.Collections.Generic;
using System.Text;
using System.Messaging;
using System.Threading;
using System.Linq;
namespace QueueTest
{
class Program
{
static void Main(string[] args)
{
using (WorkQueue q = new WorkQueue())
{
q.Finished += new Action(delegate { Console.WriteLine("All jobs finished"); });
Random r = new Random();
foreach (int i in Enumerable.Range(1, 10))
q.Enqueue(r.Next(100, 500));
Console.WriteLine("All jobs queued");
q.Start(8);
}
}
}
class WorkQueue : IDisposable
{
private Queue<int> _jobs = new Queue<int>();
private int _job_count;
private EventWaitHandle _wh = new AutoResetEvent(false);
private object _lock = new object();
private List<WorkerThread> _th;
public event Action Finished;
public WorkQueue()
{
}
public void Start(int num_threads)
{
_job_count = _jobs.Count;
_th = new List<WorkerThread>(num_threads);
foreach (int i in Enumerable.Range(1, num_threads))
{
_th.Add(new WorkerThread(i, this));
_th[_th.Count - 1].JobFinished += new Action<int>(WorkQueue_JobFinished);
}
}
void WorkQueue_JobFinished(int obj)
{
lock (_lock)
{
_job_count--;
if (_job_count == 0 && Finished != null)
Finished();
}
}
public void Enqueue(int job)
{
lock (_lock)
_jobs.Enqueue(job);
_wh.Set();
}
public void Dispose()
{
Enqueue(Int32.MinValue);
_th.ForEach(th => th.Join());
_wh.Close();
}
public int GetNextJob()
{
lock (_lock)
{
if (_jobs.Count > 0)
return _jobs.Dequeue();
else
return Int32.MinValue;
}
}
public void WaitOne()
{
_wh.WaitOne();
}
}
class WorkerThread
{
private Thread _th;
private WorkQueue _q;
private int _i;
public event Action<int> JobFinished;
public WorkerThread(int i, WorkQueue q)
{
_i = i;
_q = q;
_th = new Thread(DoWork);
_th.Start();
}
public void Join()
{
_th.Join();
}
private void DoWork()
{
while (true)
{
int job = _q.GetNextJob();
if (job != Int32.MinValue)
{
Console.WriteLine("Thread {0} Got job {1}", _i, job);
Thread.Sleep(job * 10); // in reality would to actual work here
if (JobFinished != null)
JobFinished(job);
}
else
{
Console.WriteLine("Thread {0} no job available", _i);
_q.WaitOne();
}
}
}
}
}
The worker threads are all blocking on the _q.WaitOne() call in DoWork(). Calling the thread's Join() method will deadlock, the threads never exit. You'll need to add a mechanism to signal to worker thread to exit. A ManualResetEvent, tested with WaitAny in the worker, will get the job done.
One debugging tip: get familiar with the Debug + Windows + Threads window. It lets you switch between threads and look at their call stacks. You'd have quickly found this problem by yourself.
You do a WaitOne() at the end of DoWork but you never set it after the threads start running.
Note that AutoResetEvent will go back to not set state after a 'successful' WaitOne
Your loop in your DoWork method never finishes. This will cause the thread to always be busy and this thread.Join() will block forever, waiting for it to complete.
You have a WaitOne, but I don't think it's necessary unless there is a reason you want your threadpool to stick around after your work is complete:
private void DoWork()
{
bool done = false;
while (!done)
{
int job = _q.GetNextJob();
if (job != Int32.MinValue)
{
Console.WriteLine("Thread {0} Got job {1}", _i, job);
Thread.Sleep(job * 10); // in reality would to actual work here
if (JobFinished != null)
JobFinished(job);
}
else
{
Console.WriteLine("Thread {0} no job available", _i);
done = true;
}
}
}
If you want the threads to stick around so you don't have to realloc more threads when WorkQueue.Start is called, you'd have to do something more elaborate with the AutoResetEvent.
Your main problem is the deterministic deadlock described in the other answers.
The correct way to handle it, though, is not to fix the deadlock, but to eliminate the Event altogether.
The whole idea of the Producer-Consumer model is that the clients En-queue and De-queue elements concurrently, and that's why sync mechanisms are required. If you're enqueuing all of the elements beforehand and then only dequeue concurrently, you only need a lock on the dequeue, since the "Event" is used to let "Consumers" wait for new elements to be enqueued; this will not happen in your case (based on your description).
Also, the "single responsibility" design principle suggests that the threading code should be separated from the "Blocking Queue" code. Make the "Blocking Queue" a class of its own, then use it in your thread-management class.

ManualResetEvent vs. Thread.Sleep

I implemented the following background processing thread, where Jobs is a Queue<T>:
static void WorkThread()
{
while (working)
{
var job;
lock (Jobs)
{
if (Jobs.Count > 0)
job = Jobs.Dequeue();
}
if (job == null)
{
Thread.Sleep(1);
}
else
{
// [snip]: Process job.
}
}
}
This produced a noticable delay between when the jobs were being entered and when they were actually starting to be run (batches of jobs are entered at once, and each job is only [relatively] small.) The delay wasn't a huge deal, but I got to thinking about the problem, and made the following change:
static ManualResetEvent _workerWait = new ManualResetEvent(false);
// ...
if (job == null)
{
lock (_workerWait)
{
_workerWait.Reset();
}
_workerWait.WaitOne();
}
Where the thread adding jobs now locks _workerWait and calls _workerWait.Set() when it's done adding jobs. This solution (seemingly) instantly starts processing jobs, and the delay is gone altogether.
My question is partly "Why does this happen?", granted that Thread.Sleep(int) can very well sleep for longer than you specify, and partly "How does the ManualResetEvent achieve this level of performance?".
EDIT: Since someone asked about the function that's queueing items, here it is, along with the full system as it stands at the moment.
public void RunTriggers(string data)
{
lock (this.SyncRoot)
{
this.Triggers.Sort((a, b) => { return a.Priority - b.Priority; });
foreach (Trigger trigger in this.Triggers)
{
lock (Jobs)
{
Jobs.Enqueue(new TriggerData(this, trigger, data));
_workerWait.Set();
}
}
}
}
static private ManualResetEvent _workerWait = new ManualResetEvent(false);
static void WorkThread()
{
while (working)
{
TriggerData job = null;
lock (Jobs)
{
if (Jobs.Count > 0)
job = Jobs.Dequeue();
if (job == null)
{
_workerWait.Reset();
}
}
if (job == null)
_workerWait.WaitOne();
else
{
try
{
foreach (Match m in job.Trigger.Regex.Matches(job.Data))
job.Trigger.Value.Action(job.World, m);
}
catch (Exception ex)
{
job.World.SendLineToClient("\r\n\x1B[32m -- {0} in trigger ({1}): {2}\x1B[m",
ex.GetType().ToString(), job.Trigger.Name, ex.Message);
}
}
}
}
The events are kernel primitives provided by the OS/Kernel that's designed just for this sort of things. The kernel provides a boundary upon which you can guarantee atomic operations which is important for synchronization(Some atomicity can be done in user space too with hardware support).
In short, when a thread waits on an event it's put on a waiting list for that event and marked as non-runnable.
When the event is signaled, the kernel wakes up the ones in the waiting list and marks them as runnable and they can continue to run. It's naturally a huge benefit that a thread can wake up immediately when the event is signalled, vs sleeping for a long time and recheck the condition every now and then.
Even one millisecond is a really really long time, you could have processed thousands of event in that time. Also the time resolution is traditionally 10ms, so sleeping less than 10ms usually just results in a 10ms sleep anyway. With an event, a thread can be woken up and scheduled immediately
First locking on _workerWait is pointless, an Event is a system (kernel) object designed for signaling between threads (and heavily used in the Win32 API for asynchronous operations). Therefore it is quite safe for multiple threads to set or reset it without additional synchronization.
As to your main question, need to see the logic for placing things on the queue as well, and some information on how much work is done for each job (is the worker thread spending more time processing work or on waiting for work).
Likely the best solution would be to use an object instance to lock on and use Monitor.Pulse and Monitor.Wait as a condition variable.
Edit: With a view of the code to enqueue, it appears that answer #1116297 has it right: a 1ms delay is too long to wait, given that many of the work items will be extremely quick to process.
The approach of having a mechanism to wake up the worker thread is correct (as there is no .NET concurrent queue with a blocking dequeue operation). However rather than using an event, a condition variable is going to be a little more efficient (as in non-contended cases it does not require a kernel transition):
object sync = new Object();
var queue = new Queue<TriggerData>();
public void EnqueueTriggers(IEnumerable<TriggerData> triggers) {
lock (sync) {
foreach (var t in triggers) {
queue.Enqueue(t);
}
Monitor.Pulse(sync); // Use PulseAll if there are multiple worker threads
}
}
void WorkerThread() {
while (!exit) {
TriggerData job = DequeueTrigger();
// Do work
}
}
private TriggerData DequeueTrigger() {
lock (sync) {
if (queue.Count > 0) {
return queue.Dequeue();
}
while (queue.Count == 0) {
Monitor.Wait(sync);
}
return queue.Dequeue();
}
}
Monitor.Wait will release the lock on the parameter, wait until Pulse() or PulseAll() is called against the lock, then re-enter the lock and return. Need to recheck the wait condition because some other thread could have read the item off the queue.

Categories