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

Odpowiedz Nowy wątek
2019-11-07 14:20
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";
}

Pozostało 580 znaków

2019-11-07 14:59
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."

Pozostało 580 znaków

2019-11-07 15:35
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

edytowany 1x, ostatnio: Bednarus3, 2019-11-07 16:49

Pozostało 580 znaków

2019-11-07 18:52
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);
        }           

Pozostało 580 znaków

2019-11-07 22:08
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();
edytowany 1x, ostatnio: Bednarus3, 2019-11-07 22:09

Pozostało 580 znaków

2019-11-07 23:34
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[...]-controls?redirectedfrom=MSDN

Pozostało 580 znaków

2019-11-08 12:31
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"

Pozostało 580 znaków

2019-11-08 12:38
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?

Pozostało 580 znaków

2019-11-08 13:00
0

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

Pozostało 580 znaków

2019-11-08 13:19
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).

edytowany 2x, ostatnio: Pablo8, 2019-11-08 13:24

Pozostało 580 znaków

2019-11-08 14:03
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();
edytowany 1x, ostatnio: Bednarus3, 2019-11-08 14:26
Z tym blokowaniem to ustawienie SynchronizingObject = this zapewnia, że zdarzenie Exited zostanie wykonane na tym samym wątku na którym komponent został stworzony (w tym przypadku jest to główny wątek). Po uruchomieniu WaitForExit() rozpoczyna czekanie na zakończenie procesu (czeka na głównym wątku). Możliwe, że blokują się wzajemnie. - Pablo8 2019-11-08 15:09
To z nowym wątkiem działa ponieważ uruchamiasz to na nowym wątku i tam wołasz WaitForExit. Ten watek zostanie zablokowany, a nie ten główny. W trybie debug możesz to sprawdzić. W Visual Studio podczas włączonego debugowania włączasz okno z wątkami (Debug --> Windows --> Threads). Jak jesteś w tym samym wątku to wykonujesz richTextBox1.Text += e.Data. richTextBox1.InvokeRequired zwróci czy musi zostać użyty Invoke - Pablo8 2019-11-08 15:30

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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

Użytkownik: Pablo8