Вызов Process.WaitForExit () в событии по нажатию кнопки блокирует программу - PullRequest
0 голосов
/ 08 ноября 2019

Я написал то же приложение, используя Windows Forms для запуска процесса после нажатия кнопки и считывания стандартного вывода. Когда я вызываю метод «test ()» в button1_Click (), моя программа блокируется. Но когда я вызываю «test ()» в конструкторе «Form1», все работает как положено. Где проблема?

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 = "/?";

        }

        public void button1_Click(object sender, EventArgs e)
        {

        }

        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;

            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";
        }
    }
}

1 Ответ

0 голосов
/ 12 ноября 2019

Исправляя все недостающие части в вашем примере кода, нет никаких проблем (кроме того, конечно, что вы не захотите WaitForExit в приложении с графическим интерфейсом):

// Warning - The code below is WRONG! Awful even, since it will _appear_ to work in some cases.
void Main()
{
    Application.Run(new Form1());
}

public class Form1 : Form
{
    Process process1 = new Process();
    Button button1;
    RichTextBox richTextBox1;

    public Form1()
    {
        button1 = new Button { Text = "Run" };
        button1.Click += button1_Click;
        Controls.Add(button1);

        richTextBox1 = new RichTextBox { Left = 100 };
        Controls.Add(richTextBox1);

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

    public void button1_Click(object sender, EventArgs e)
    {
        test();
    }

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

        process1.OutputDataReceived += process1_OutputDataReceived;
        process1.ErrorDataReceived += process1_ErrorDataReceived;

        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;

        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";
    }
}

Однако,это все еще то, что вы не хотите делать. Элементы управления графическим интерфейсом не должны быть доступны из другого потока, чем они были созданы. У меня нет проблем с тестовым приложением, но даже если оно работает, это просто катастрофа, ожидающая, что произойдет.

Бывает, что RichTextBox имеет немного странную обработку свойства Text,Если дескриптор еще не создан, Text меняет только одно поле в классе. Он не выполняет многопоточной проверки доступа или чего-либо еще. Теперь, когда дескриптор фактически создается, значение из этого поля применяется к тексту элемента управления для реального - в правильном потоке.

Возможно, именно поэтому вы видите свое странное поведение. В конструкторе формы дескриптор еще не создан. Доступ из фонового потока - все еще плохая идея, но в большинстве случаев он ничего не нарушает (вы можете потерять часть выходных данных процесса). Когда форма отображается, маркеры создаются, и в текстовом поле отображается «правильный» вывод.

Когда форма уже уже создана (и показана), это изменится. В однопоточном потоке квартиры вы должны справиться с многопоточностью самостоятельно. Вы не должны получать доступ к элементу управления из другого потока - и большинство элементов управления будут вызывать вас и выдавать исключение. По какой-то причине RichTextBox не является (всегда) таким контролем. Таким образом, в STA код работает - не надежно и не безопасно, но не зависает и не падает.

Но ваш поток пользовательского интерфейса, кажется, находится вквартира с резьбой. А в MTA, когда вы пытаетесь назначить текст для RichTextBox (с созданным дескриптором), сообщение направляется в поток пользовательского интерфейса. Но ваш поток пользовательского интерфейса занят ожиданием завершения процесса! Таким образом, вы зависаете.

Как решить эту проблему?

  1. Никогда не обращайтесь к элементам управления графическим интерфейсом из потока, отличного от того, в котором они были созданы. Маршал вызов явно (например, используя Invoke), и вы получите надежное и последовательное поведение.
  2. Не блокируйте поток пользовательского интерфейса. Process имеет Exited даже, что позволяет вам реагировать на процесс, выходящий асинхронно.
...