dwmkerr / consolecontrol

ConsoleControl is a C# class library that lets you embed a console in a WinForms or WPF application.
MIT License
723 stars 169 forks source link

BackgroundWorker is busy even after IsProcessRunning is false #51

Open Loucool111 opened 4 years ago

Loucool111 commented 4 years ago

Hello,

So basically i'm looping over some array and for each item I want to start a new process and when it ends, carry on with the next one...

Here is the code (I'm calling this from another thread) the view.console object is ConsoleControl.WPF

foreach (string word in words)
{
    bool isRunning = view.console.Dispatcher.Invoke(() => view.console.IsProcessRunning);

    while (isRunning)
    {
        //Wait and query again after 250ms
        //Thread.Sleep(250);
        isRunning = view.console.Dispatcher.Invoke(() => view.console.IsProcessRunning);
    }

    view.console.Dispatcher.Invoke(new Action<string, string>(view.console.StartProcess), executablePath, cliArguments + word);
}

The only way I could get it to work is with that Thread.Sleep() in the while loop.

Am I doing something wrong, or is this a bug? Btw, when I run the code in step-by-step in VS it works fine.

dwmkerr commented 4 years ago

Hi @Loucool111 I would have to do a real deep dive here to understand why you don't get a change in value, but I expect it is down to the fact that you are essentially absolutely thrashing the main program thread because you are running a constant loop, with no breaks. In general, any kind of loop like this:

while(somethingThatIsTrueForALongTime)
{
  somethingThatIsQuickToDo();
}

Is going to quickly put the CPU to 100%, you don't give it any time to switch to other threads in the process. Running in the debugger means again you are probably artificially slowing things down enough. So even if you use this code, I would at least sleep for 1ms.

However, why not do something like this?

Stack<string> words = new Stack<String>(new[] { "one", "two", "three" });

        void ProcessWords()
        {
            //  Whenever a process is finished, if we still have words left to process, pop the stack
            //  and start processing the next word.
            processInterface.OnProcessExit += (sender, args) =>
            {
                if (words.Any())
                {
                    processInterface.StartProcess("someprogram", words.Pop());
                }
            };
           // Now just start
           StartProcess("something", words.Pop());
        }

You can get processInterface with view.console.ProcessInterface. Essentially what you are doing here is waiting for a process to exit. When it exists, if there are any words to process, start processing the next available.

Werwolf696 commented 4 years ago

Yeah no this doesn't work at all... if I call processInterface.StartProcess() and then check "IsProcessRunning" it is false... so it seems you cannot tell when to issue the next processInterface.StartProcess(). You should have used a callback system where you could set a callback function to execute when the process finishes.

AdamKlob commented 2 years ago

I'm experiencing similar problem. What I also see, that the process starts, but does not seem to be running. I've created following workaraoud:

` public void StartProcess(string name, string args) { while(true) { try { consoleControl1.StartProcess(name, args); } catch (System.InvalidOperationException ex) { consoleControl1.StopProcess(); Application.DoEvents(); continue; }

            break;
        }`
AdamKlob commented 2 years ago

It seems that the process when exits there is still (buffered?) lines to be sent to the richtextbox, and worker is busy with that.