Skip to main content
1 of 2
Oguz Ozgul
  • 853
  • 5
  • 9

In a context where thread affinity is required and a SynchronizationContext is not available (as in console applications or windows services), a blocking implementation is required.

I have just a few recommendations:

WaitHandle.WaitAny(new WaitHandle[] {killSubProc, channelWaitHandle});

This wait can cause an issue. If the remote application sends a message and while execution of xxx() continues on the main thread, another message is received, that new message will not be processed until another new message is received.

Main Thread   -> Wait               xxx()----------------- WaitAny       xxx()
Thread X      ->         Message Set
Thread Y      ->                          Message Set                     
Thread Z      ->                                              Message Set
   

Another suggestion:

Move the queue and queueing/dequeueing logic into a class to de-couple it's implementation from invocation of XXX (Make a reusable blocking queue)

Here we have a very generic Message class to store the callback handler and the payload (e.data in your case)

internal class Message
{
    private readonly SendOrPostCallback handler;
    private readonly object payload;

    internal Message(SendOrPostCallback handler, object payload)
    {
        this.handler = handler;
        this.payload = payload;
    }

    internal void Execute()
    {
        handler(payload);
    }
}

And another very short class for keeping track of the queue and to execute the operations (XXX in your case). Please check the use of Queue.Synchronized

public class BlockingMessageSynchronizer
{
    private Queue synchronizationQueue;
    private bool stopRequested;

    public void Start()
    {
        lock (this)
        {
            if (synchronizationQueue != null)
            {
                throw new InvalidOperationException("The synchronization has already started.");
            }
            synchronizationQueue = Queue.Synchronized(new Queue());
            stopRequested = false;
        }

        while (!stopRequested)
        {
            // Thread.Sleep(1);
            ProcessQueue();
        }

        ProcessQueue();
        synchronizationQueue = null;
    }

    public void Post(SendOrPostCallback messageHandler, object payload)
    {
        if (synchronizationQueue == null || stopRequested)
        {
            lock (this)
            {
                if (synchronizationQueue == null || stopRequested)
                {
                    throw new InvalidOperationException("The synhronization is not started or is stopped");
                }
            }
        }
        synchronizationQueue.Enqueue(new Message(messageHandler, payload));
    }

    public void Stop()
    {
        stopRequested = true;
    }

    private void ProcessQueue()
    {
        while (synchronizationQueue.Count > 0)
        {
            ((Message)synchronizationQueue.Dequeue()).Execute();
        }
    }
}

This class can be used on ay thread to force execution of the callback handler on that thread, by blocking that thread. This implementation will cause 100% cpu since there is no Wait operations, you can consider Thread.Sleep(1) before the call to ProcessQueue() in the while loop in Start()

And finally, your stdout or stderr handlers will look like:

process.OutputDataReceived += (sender, e) =>
{
    if (e.Data != null)
    {
        synchronizer.Post(HandleCrossThreadMessage, e.Data);
    }
};

process.ErrorDataReceived += (sender, e) =>
{
    if (e.Data != null)
    {
        synchronizer.Post(HandleCrossThreadMessage, "STDERR");
        synchronizer.Post(HandleCrossThreadMessage, e.Data);
    }
};

process.Exited += (sender, e) =>
{
    synchronizer.Post(HandleCrossThreadMessage, "EXIT");
    synchronizer.Stop();
};

BlockingMessageSynchronizer synchronizer = new BlockingMessageSynchronizer();

process.Start();

process.BeginOutputReadLine();
process.BeginErrorReadLine();

synchronizer.Start();

When you call synchronizer.Stop(); inside the exit event, the remaining messages in the queue will be processed and the execution will get out of the synchronizer.Start(); blocking call.

Oguz Ozgul
  • 853
  • 5
  • 9