Metoda WaitForExit() w klasie Proscess blokuje działaanie programu

0

Próbuję przechwycić wyjście standardowe aplikacji konsolowej. Wywołanie metody WaitForExit() blokuje działanie programu i nic się nie wyświetla w "richTextBox1". Jak usunę wywołanie tej metody to wtedy wszystko działa. Jednak zależy mi na użyciu WaitForExit();

private void button1_Click(object sender, EventArgs e)
{
	process1.StartInfo.FileName = "cmd.exe";
	process1.StartInfo.Arguments = "/?";

	process1.Start();
	process1.BeginOutputReadLine();
	process1.BeginErrorReadLine();
	
	process1.WaitForExit();
}

private void process1_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
	richTextBox1.Text += e.Data + "\n";
}

private void process1_ErrorDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
	richTextBox1.Text += e.Data + "\n";
}
0

W dokumentacji dla metody Process.WaitForExit jest napisane, że wątek będzie czekał na zakończenie tego procesu. Wywołując cmd.exe /? na końcu pojawia się "Press any key to continue...". Proces nie zakończy działania do czasu naciśnięcia dowolnego przycisku.

Fragment z dokumentacji:
"WaitForExit() makes the current thread wait until the associated process terminates."

0

Napisałem program testowy

namespace test
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("OK");
        }
    }
}

Gdy wywołuję test.exe też się blokuje

0

Taki kod działa i WaitForExit() nie blokuje programu. Ten komunikat na końcu "Press any key to continue..." jednak nie blokuje.

       public static void Main(string[] args)
       {
            var p = new Process
            {
                StartInfo =
                {
                    RedirectStandardError = true,
                    RedirectStandardOutput = true,
                    UseShellExecute = false,
                    FileName = "cmd.exe",
                    Arguments = "/?"
                }
            };

            p.OutputDataReceived += OutputDataReceived;
            p.ErrorDataReceived += ErrorDataReceived;
            
            p.Start();
            
            p.BeginOutputReadLine();
            p.BeginErrorReadLine();
            
            p.WaitForExit();
        }

        private static void OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data)) return;
            
            Console.WriteLine(e.Data);
        }

        private static void ErrorDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data)) return;
            
            Console.WriteLine(e.Data);
        }       	
0

Poniższy kod działa:

using System;
using System.Windows.Forms;

namespace DISMassistant
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            process1.StartInfo.RedirectStandardError = true;
            process1.StartInfo.RedirectStandardOutput = true;
            process1.StartInfo.UseShellExecute = false;
            process1.StartInfo.FileName = "cmd.exe";
            process1.StartInfo.Arguments = "/?";

            process1.Start();

            process1.BeginOutputReadLine();
            process1.BeginErrorReadLine();

            process1.WaitForExit();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("OK");
        }

        private void process1_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data)) return;
            richTextBox1.Text += e.Data + "\n";
        }

        private void process1_ErrorDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data)) return;
            richTextBox1.Text += e.Data + "\n";
        }
    }
}

Ale jak przeniosę poniższy fragment do metody "button1_Click" to program się zawiesza

process1.Start();

process1.BeginOutputReadLine();
process1.BeginErrorReadLine();

process1.WaitForExit();
0

W twoim kodzie brakuje:
process1.OutputDataReceived += process1_OutputDataReceived;
process1.ErrorDataReceived += process1_ErrorDataReceived;
oraz
private Process process1 = new Process();

Po dodaniu tych linijek i uruchomieniu nie udało mi się powtórzyć problemu.
Jedynie w trybie debug próbuje modyfikować RichTextBoxa z innego wątku. Tutaj jest opis jak to poprawić w takim przypadku:
https://docs.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls?redirectedfrom=MSDN

0

Ja korzystam z Visual Studio Express 2017. Aplikacja jest typu Windows Forms. Wyszczególnione przez ciebie linijki są w kodzie, tylko w innym pliku. Zostały one dodane automatycznie poprzez przeciągnięcie kontrolki do formularza. Poza tym przechwycenie wyjścia standardowego działa jak jego obsługa jest umieszczona bezpośrednio w konstruktorze klasy "Form1". A nie działa w wywołaniu metody "button1_Click"

0

Wczoraj stworzyłem też aplikacje Windows Forms i na tej aplikacji sprawdzałem to.

Mógłbyś podesłać cały projekt to sprawdzę na nim?

0

Taki kod działa (w załączniku). Tylko je chcę, aby to się odbyło po przyciśnięciu "Button1"

0

Zatrzymuje się przez to (jest to w Form1.Designer.cs): this.process1.SynchronizingObject = this;
Usunięcie tego albo process1.WaitForExit() nie powoduje blokowania programu.

Po usunięciu this.process1.SynchronizingObject z innego wątku będzie próbował modyfikować RichTextBoxa. W poprzednim komentarzu wrzuciłem link do dokumentacji, musisz użyć metody Invoke.
Po usunięciu process1.WaitForExit() działa dobrze (widać jedynie odświeżenie tekstu jak dodaje nowe linie z tekstem do RichTextBoxa).

0

Usunąłem "this.process1.SynchronizingObject = this;" zastosowałem "invoke" Niestety poniższy kod dalej blokuje program, a nie mogę usunąć "process1.WaitForExit();", bo proces który chcę uruchomić wykonuje się bardzo długo, a program musi poczekać na wyjście standardowe tego procesu.

using System;
using System.Windows.Forms;

namespace DISMassistant
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            process1.StartInfo.RedirectStandardError = true;
            process1.StartInfo.RedirectStandardOutput = true;
            process1.StartInfo.UseShellExecute = false;
            process1.StartInfo.FileName = "test.exe";
            //process1.StartInfo.Arguments = "/?";
        }

        public void button1_Click(object sender, EventArgs e)
        {
            //MessageBox.Show("OK");
            process1.Start();

            process1.BeginOutputReadLine();
            process1.BeginErrorReadLine();

            process1.WaitForExit();
        }

        private void process1_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data)) return;
            
            richTextBox1.Invoke((MethodInvoker)delegate { richTextBox1.Text += e.Data + "\n"; });
        }

        private void process1_ErrorDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data)) return;

            richTextBox1.Invoke((MethodInvoker)delegate { richTextBox1.Text += e.Data + "\n"; });
        }
    }
}

Poniższe rozwiązanie poprzez uruchomienie procesu w osobnym wątku rozwiązuje problem. Ale chciałbym wiedzieć dlaczego w konstruktorze "Form1" działa, a w "button1.Click()" już nie, jak jest wywoływane w tym samym wątku.

Thread th = new Thread(() =>
            {
                process1.Start();

                process1.BeginOutputReadLine();
                process1.BeginErrorReadLine();

                process1.WaitForExit();
            });
            th.Start();
0

To jak chcesz wiedzieć kiedy proces zakończy działanie i ma nie blokować aktualnego wątku to w dokumentacji przy metodzie Process.WaitForExit jest: "To avoid blocking the current thread, use the Exited event.".

w Form1.Designer.cs dodajesz:

this.process1.EnableRaisingEvents = true;
this.process1.Exited += new EventHandler(this.process1_ProcessExitHandler);

a w Form1.cs

private void process1_ProcessExitHandler(object sender, EventArgs e)
{
    
}
0

Dzięki Pablo8. Wykorzystanie zdarzenia "Exited" obeszło problem. Ale i tak nie rozumiem czemu poniższy kod działa w 100%, tzn.: Proces się kończy. W związku z czym "WaitForExit() też się kończy i nie blokuje niczego. Wyjście standardowe procesu zostaje skopiowane do richTextBox oraz wyświetla się "messageBox" generowany w zdarzeniu "Exited"

using System;
using System.Windows.Forms;

namespace DISMassistant
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            process1.StartInfo.RedirectStandardError = true;
            process1.StartInfo.RedirectStandardOutput = true;
            process1.StartInfo.UseShellExecute = false;
            process1.StartInfo.FileName = "cmd.exe";
            process1.StartInfo.Arguments = "/?";

            test();
        }

        public void test()
        {
            process1.Start();

            process1.BeginOutputReadLine();
            process1.BeginErrorReadLine();

            process1.WaitForExit();
            process1.CancelOutputRead();
            process1.CancelErrorRead();
            process1.Close();
        }

        private void process1_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data)) return;

            if (richTextBox1.InvokeRequired)
            {
                richTextBox1.Invoke((MethodInvoker)delegate { richTextBox1.Text += e.Data + "\n"; });
            }
            else
            {
                richTextBox1.Text += e.Data + "\n";
            }
        }

        private void process1_ErrorDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data)) return;

            if (richTextBox1.InvokeRequired)
            {
                richTextBox1.Invoke((MethodInvoker)delegate { richTextBox1.Text += e.Data + "\n"; });
            }
            else
            {
                richTextBox1.Text += e.Data + "\n";
            }
        }

        private void process1_Exited(object sender, EventArgs e)
        {
            process1.CancelOutputRead();
            process1.CancelErrorRead();
            MessageBox.Show("Proces zakończył się");
        }
    }
}

Natomiast w poniższym kodzie proces nie kończy się, dlatego wykonanie programu zatrzymuje się na "WaitForExit()", i nawet formularz się nie wyświetla. Różnica między kodami jest taka, że w pierwszym przypadku wywołuję metodę "test()" w konstruktorze klasy "Form1", a w drugim w zdarzeniu "Load" formularza.

using System;
using System.Windows.Forms;

namespace DISMassistant
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            process1.StartInfo.RedirectStandardError = true;
            process1.StartInfo.RedirectStandardOutput = true;
            process1.StartInfo.UseShellExecute = false;
            process1.StartInfo.FileName = "cmd.exe";
            process1.StartInfo.Arguments = "/?";
        }
		
        private void Form1_Load(object sender, EventArgs e)
        {
            test();
        }

        public void test()
        {
            process1.Start();

            process1.BeginOutputReadLine();
            process1.BeginErrorReadLine();

            process1.WaitForExit();
            process1.CancelOutputRead();
            process1.CancelErrorRead();
            process1.Close();
        }

        private void process1_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data)) return;

            if (richTextBox1.InvokeRequired)
            {
                richTextBox1.Invoke((MethodInvoker)delegate { richTextBox1.Text += e.Data + "\n"; });
            }
            else
            {
                richTextBox1.Text += e.Data + "\n";
            }
        }

        private void process1_ErrorDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data)) return;

            if (richTextBox1.InvokeRequired)
            {
                richTextBox1.Invoke((MethodInvoker)delegate { richTextBox1.Text += e.Data + "\n"; });
            }
            else
            {
                richTextBox1.Text += e.Data + "\n";
            }
        }

        private void process1_Exited(object sender, EventArgs e)
        {
            process1.CancelOutputRead();
            process1.CancelErrorRead();
            MessageBox.Show("Proces zakończył się");
        }
    }
}
0

W obu przypadkach na głównym wątku wywołuje się process1.WaitForExit() i ten wątek zostaje zablokowany.
W pierwszym przypadku do metody process1_OutputDataReceived wchodzi z innego wątku i z tego wątku może dodać tekst do richTextBoxa.
W drugim przypadku proces cały czas się wykonuje, a do metody process1_OutputDataReceived nie wchodzi, ponieważ na innym wątku wykonuje cały czas funkcję OutputReadNotifyUser. Próbuje odwołać się (Invoke) do wątku głównego, który jest zablokowany.

0

Tylko w pierwszym przypadku uruchamiany proces się kończy o czym świadczy wystąpienie zdarzenia "Exited", więc prosess1."WaitForExit()" nie blokuje niczego.

0

WaitForExit() blokuje tylko do czasu zakończenia procesu. Zdarzenie Exited jest wykonywane po zakończeniu procesu, więc w tym momencie wątek nie jest już zablokowany, bo proces skończył już swoje działanie.

Zablokowanie programu nastąpi w takiej sytuacji:
Próbujesz zmodyfikować richTextBox.Text, ale nie możesz tego zrobić z aktualnego wątku i musisz odwołać się do innego wątku. Jeśli na tym innym wątku jest wywołana funkcja WaitForExit(), która blokuje wątek to wywołując Invoke z aktualnego wątku zablokujesz też ten wątek. W takim przypadku WaitForExit nigdy się nie skończy bo proces nie zakończy swojego działania.

1 użytkowników online, w tym zalogowanych: 0, gości: 1