Search code examples
c#.netmultithreadingasynchronous.net-3.5

I feel like I'm re-inventing the wheel. Dispatch work to a specific thread. I want to process events from multiple threads sequentially on one thread


I know that similar things exist in WPF and forms applications with the Control.Invoke method, I also know of the existence of BackgroundWorker, ThreadPool etc.

However, I don't want to depend on Forms/WPF, and I want to make sure work is executed sequentially and on one thread.

Edit: Rationale: I want to drive a state machine from one thread. The events come from other threads tough. There is no UI.

So far I couldn't really figure out how to do this with existing framework classes but I might have misunderstood the documentation.

Edit: I forgot to mention I'm bound to .NET Framework 3.5

What I wrote so far:

public class Dispatcher
{
    string Name;
    Thread WorkerThread;
    Queue<Action> WorkQueue;
    List<Exception> Exceptions;
    ManualResetEvent Gate;
    volatile bool KeepRunning;
    readonly object WorkLocker;

    public override string ToString()
    {
        return String.Format("{0}({1})", this.GetType().Name, Name);
    }

    public Dispatcher(string name)
    {
        Name = name;
        WorkLocker = new Object();
        Gate = new ManualResetEvent(false);
        WorkQueue = new Queue<Action>();
        Exceptions = new List<Exception>();
    }

    public void Start()
    {
        if (WorkerThread == null)
        {
            WorkerThread = new Thread(doDispatch)
            {
                IsBackground = true,
                Name = this.Name
            };
            WorkerThread.Start();
        }
    }

    public void Stop()
    {
        if (WorkerThread != null && WorkerThread.IsAlive)
        {
            Dispatch(() => { KeepRunning = false; });
            WorkerThread.Join();
        }
        WorkerThread = null;
    }

    public void Reset()
    {
        Stop();
        lock (WorkLocker)
        {
            WorkQueue = new Queue<Action>();
            Exceptions = new List<Exception>();
        }
    }

    public void Dispatch(Action a)
    {
        lock (WorkLocker)
        {
            WorkQueue.Enqueue(a);
        }
        Gate.Set();
    }

    public List<Exception> CollectExceptions()
    {
        List<Exception> result = new List<Exception>();
        lock(WorkLocker)
        {
            foreach(Exception e in Exceptions)
            {
                result.Add(e);
            }
            Exceptions.Clear();
        }
        return result;
    }

    private void doDispatch()
    {
        KeepRunning = true;
        while (KeepRunning)
        {
            Gate.WaitOne();
            lock (WorkLocker)
            {
                while (WorkQueue.Count > 0)
                {
                    try
                    {
                        WorkQueue.Dequeue()?.Invoke();
                    }
                    catch (Exception e)
                    {
                        Exceptions.Add(e);
                    }
                }
            }
        }
    }
}

Is there a way to do something like this in a simpler way? Another nice feature would be being able to dispatch calls that have multiple arguments.


Solution

  • Since you are bound to 3.5 you can't use BlockingCollection or the DataFlow library...you'll have to roll your own implementation.

    The sample code you provided is a good start, but you should apply the Single Responsibility Principle to make it cleaner and easier to refactor when(if?) you upgrade the .NET Framework.

    I would do it like this:

    • Create a thread safe wrapper class around Queue that somewhat mimics BlockingCollection, this answer provides a nice example
    • Structure your code around a consumer/producer flow and inject the wrapper