【问题标题】:Calling Process.WaitForExit() in button clicked event is blocking program在按钮单击事件中调用 Process.WaitForExit() 正在阻塞程序
【发布时间】:2020-03-05 04:42:15
【问题描述】:

我使用 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";
        }
    }
}

【问题讨论】:

  • 不要使用 WaitForExit(),支持 Process.Exited 事件。或者根本不要等待,无论如何 process1 对象不再可用,因此清理没有那么有用。想知道你是否真的打算这样做。
  • 这就是 WaitForExit 方法的工作原理。它会暂停当前线程,直到检测到远程进程已退出。
  • 但是当我这样称呼它时它什么都没有阻塞:code public Form1() { InitializeComponent(); process1.StartInfo.RedirectStandardError = true; process1.StartInfo.RedirectStandardOutput = true; process1.StartInfo.UseShellExecute = false; process1.StartInfo.FileName = "cmd.exe"; process1.StartInfo.Arguments = "/?"; test(); }
  • 您可以打印出两个调用的当前线程的名称,看看它们是否不同。很可能您的构造函数由与您的 button_click 处理程序不同的线程执行,该处理程序由 ui 线程调用。
  • 你在哪里分配OutputDataReceived事件?

标签: c# buttonclick waitforexit


【解决方案1】:

修复示例代码中所有缺失的部分,没有问题(当然,除了您不想在 GUI 应用程序中 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";
    }
}

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

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

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

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

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

你是怎么解决这个问题的?

  1. 切勿从与创建它们的线程不同的线程访问 GUI 控件。显式编组调用(例如使用 Invoke),您将获得可靠且一致的行为。
  2. 不要阻塞 UI 线程。 Process 有一个 Exited 甚至可以让您对以异步方式退出的进程做出反应。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-07-15
    • 2018-06-02
    • 1970-01-01
    • 1970-01-01
    • 2017-07-30
    • 1970-01-01
    • 2021-04-18
    相关资源
    最近更新 更多