Multiple producer single consumer on the same thread I implemented a multi-producer single consumer class, with an important restriction that once the consumer started working, it must continue its work on the same thread (the reasoning behind this is to allocate and free COM objects from a third party library, and these actions must be on the same thread according to that library).
Here's the code, followed by an example:
interface WorkerLogic&lt;T&gt;
{
    void Work(T item);
}

class ThreadedWorker&lt;T&gt; where T : class
{
    private readonly object locker = new object();
    private readonly Queue&lt;T&gt; queue = new Queue&lt;T&gt;();
    private readonly WorkerLogic&lt;T&gt; logic;
    private readonly Thread actualThread;
    private bool started = false;
    private readonly ManualResetEvent resetEvent = new ManualResetEvent(false);
    private volatile bool shouldWork = true;

    public ThreadedWorker(WorkerLogic&lt;T&gt; logic)
    {

        this.logic = logic;
        actualThread = new Thread(Spin);
    }

    private void Spin()
    {
        while (shouldWork)
        {
            resetEvent.WaitOne();
            while (shouldWork)
            {
                T item;
                lock (locker)
                {
                    if (queue.Count == 0)
                        break; // back to main loop and WaitOne
                    item = queue.Dequeue();
                    resetEvent.Reset();
                }
                try
                {
                    logic.Work(item);
                }
                catch (Exception ex)
                {
                    // log
                }
            }
        }
    }

    public void Stop()
    {
        Stop(TimeSpan.FromMilliseconds(500));
    }

    public void Stop(TimeSpan timeout)
    {
        shouldWork = false;
        lock (locker)
        {
            resetEvent.Set();
        }
        bool joined = actualThread.Join(timeout);
        if (!joined)
        {
            try
            {
                actualThread.Abort();
            }
            catch (ThreadStateException)
            {
                // swallow
            }
        }
    }


    public void Push(T item)
    {
        lock (locker)
        {
            queue.Enqueue(item);
            if (!started)
            {
                started = true;
                actualThread.Start();
            }
            resetEvent.Set();
        }
    }
}

Usage example for the code above:
public class UsageExample
{
    public class MyLogic : WorkerLogic&lt;string&gt;
    {
        public void Work(string s)
        {
            Console.WriteLine(&quot;working on '{0}', thread id = {1}&quot;, s, Thread.CurrentThread.ManagedThreadId);
        }
    }

    public static void Main()
    {
        ThreadedWorker&lt;string&gt; threadedWorker = new ThreadedWorker&lt;string&gt;(new MyLogic());
        threadedWorker.Push(&quot;My&quot;);
        threadedWorker.Push(&quot;name&quot;);
        threadedWorker.Push(&quot;is&quot;);
        threadedWorker.Push(&quot;Luca&quot;);
        threadedWorker.Stop();
    }

}

Example output:
working on 'My', thread id = 3
working on 'name', thread id = 3
working on 'is', thread id = 3
working on 'Luca', thread id = 3

I created a repl for this, feel free to play with it.
I'd like to have a peer review focused on correctness: is it thread safe? Am I missing an edge case?
Thanks!
I took rob.earwaker's advice (thanks!), and I implemented it using a BlockingCollection. The code is much simpler now :)
class ThreadedWorker&lt;T&gt; where T : class
{
    private readonly object locker = new object();
    BlockingCollection&lt;T&gt; queue = new BlockingCollection&lt;T&gt;(new ConcurrentQueue&lt;T&gt;());
    private readonly WorkerLogic&lt;T&gt; logic;
    private readonly Thread actualThread;
    private bool started = false;
    private volatile bool shouldWork = true;

    public ThreadedWorker(WorkerLogic&lt;T&gt; logic)
    {

        this.logic = logic;
        actualThread = new Thread(Spin);
        
    }

    private void Spin()
    {
        while (shouldWork)
        {
            T item;
            var dequeued = queue.TryTake(out item, TimeSpan.FromMilliseconds(100));
            if (!dequeued) continue;
            try
            {
                logic.Work(item);
            }
            catch (Exception ex)
            {
                // log
            }

        }            
    }

    public void Stop()
    {
        Stop(TimeSpan.FromMilliseconds(500));
    }

    public void Stop(TimeSpan timeout)
    {
        shouldWork = false;            
        bool joined = actualThread.Join(timeout);
        if (!joined)
        {
            try
            {
                actualThread.Abort();
            }
            catch (ThreadStateException)
            {
                // swallow
            }
        }
    }


    public void Push(T item)
    {
        if (!started)
        {
            lock (locker)
            {
                if (!started)
                {
                    started = true;
                    actualThread.Start();
                }
            }
        }
        queue.Add(item);
    }
}