0

在按下按钮并读取其标准输出后,我使用 Windows 窗体编写了相同的应用程序来运行进程。当我在 button1_Click() 中调用方法“test()”时,我的程序被阻塞了。但是当我在“Form1”构造函数中调用“test()”时,一切都按预期工作。问题出在哪里?

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

1 回答 1

0

修复示例代码中所有缺失的部分,没有问题(当然,除了您不想WaitForExit在 GUI 应用程序中):

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

但是,这仍然是您不想做的事情。不应从与创建它们的线程不同的线程访问 GUI 控件。我的测试应用程序没有问题,但即使它有效,这只是等待发生的灾难。

碰巧对财产RichTextBox的处理有点奇怪。Text如果尚未创建句柄,则Text仅更改类中的单个字段。它不做任何多线程访问检查或任何事情。现在,当实际创建句柄时,该字段的值将真正应用于控件的文本 - 在正确的线程上。

这可能就是为什么你看到你的奇怪行为的原因。在表单构造函数中,尚未创建句柄。从后台线程访问仍然是一个坏主意,但它实际上并没有破坏任何东西,大多数时候(您可能会丢失一些进程输出)。显示表单时,将创建句柄,并且文本框显示“正确”输出。

当表单已经创建(并显示)时,这会发生变化。在单线程单元线程上,您需要自己处理多线程。您不应该从不同的线程访问控件 - 大多数控件会调用您并引发异常。无论出于何种原因,RichTextBox不是(总是)那种控制。因此,在 STA 上,代码恰好可以工作——不可靠或不安全,但它不会挂起,也不会崩溃。

但是您的 UI 线程似乎位于多线程单元中。而在 MTA 中,当您尝试将文本分配给RichTextBox(使用创建的句柄)时,消息将被编组到 UI 线程。但是您的 UI 线程正忙于等待进程退出!因此,你挂了。

你如何解决这个问题?

  1. 切勿从与创建它们的线程不同的线程访问 GUI 控件。显式地编组调用(例如使用Invoke),您将获得可靠且一致的行为。
  2. 不要阻塞 UI 线程。甚至可以让您对以异步方式退出的进程做出反应ProcessExited
于 2019-11-12T09:34:50.697 回答