Felix ZY
Felix ZY

Reputation: 976

Re-synchronize Process.RedirectStandardOutput

Background

I'm writing a c# wrapper for a node.js application. In this wrapper I continuously read the standard output via Process.RedirectStandardOutput. The event is bound to the function onOutputDataReceived, in an instance of the class ProcessManager. In this same instance, there is also an instance of a custom event system.

[ProcessManager]

EventSystem eventSystem;

private void Start()
{
    [...]

    process.OutputDataReceived += onOutputDataReceived;

    [...]
}

private void onOutputDataReceived(object sender, DataReceivedEventArgs e)
{
    [...]

    eventSystem.call(eventName, args);
}

[EventSystem]

List<EventHandler> eventList;

public Boolean call(String eventName, dynamic args)
{
    [...]

    foreach (EventHandler handler in eventList)
    {
        handler(args);
    }

    [...]
}

The problem occurs when the event is being called. Here is an example from a winforms application using my wrapper.

Wrapper.ProcessManager procMan;

procMan.eventSystem.on(eventName, (a) =>
    {
        button1.Text = someValue;
    });

When run, the application crashes with the message

Cross-thread operation not valid: Control 'button1' accessed from a thread other than the thread it was created on

My issue, as I understand it, is this:

onOutputDataReceived is being executed asynchronously, in its own thread. As this same thread, only meant to be handling the output, goes on to call the events, I'm unintentionally multithreading my wrapper, making life harder for anyone implementing it.

Basically,

I need to run the line eventSystem.call() in the same thread that maintains the rest of the ProcessManager instance, as soon as new output data has been received as possible. Any ideas on how this best can be achieved?


A solution I've thought of is something like this

[ProcessManager]

Queue<string> waiting = new Queue<string();
EventSystem eventSystem;

private void onOutputDataReceived(object sender, DataReceivedEventArgs e)
{
    [...]
    waiting.Enqueue(eventName);
}

private void WhenReady()
{
    while(waiting.Count > 0)
        eventSystem.call(waiting.Dequeue());
}

As far as I can see, this would involve some kind of polling every x milliseconds, which doesn't feel like a clean solution. Also, it seems to me as if such a solution would be way too expensive for when no messages are being received and too slow for when some are.

Upvotes: 2

Views: 70

Answers (1)

usr
usr

Reputation: 171178

The code that executes the nodejs process and reads its output should not need to know about the threading requirements of event subscribers. Make the subscriber satisfy its own requirements:

(a) =>
{
    Invoke(new Action(() => button1.Text = someValue)); //marshal to UI thread
}

Your tentative solution would not work because it would block the UI thread.

Also, waiting is being used in an unsynchronized way... This is an unrelated bug.

Upvotes: 2

Related Questions