将批处理文件echo命令重定向到TextBox会在Dispatcher.Invoke调用上挂起

时间:2015-11-03 19:34:14

标签: c# wpf batch-file

我有一个小应用程序,它有一个信息文本框,可以输出执行的命令。

目前我进行了设置,以便Console.Write[WriteLine]正确添加到文本框中。该代码如下:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        // bind the console output to the new text box
        var writer = new TextBoxStreamWriter(x_OutputTextBox);
        Console.SetOut(writer);
        Console.SetError(writer);            
    }
}

internal class TextBoxStreamWriter : TextWriter
{
    static TextBox _text = null;

    public TextBoxStreamWriter(TextBox outputBox)
    {
        _text = outputBox;
    }

    public override void Write(string value)
    {
        base.Write(value);
        _text.Dispatcher.Invoke(() => _text.AppendText(string.Format("{0} - {1}", DateTime.Now, value)));
    }

    public override void WriteLine(string value)
    {
        base.WriteLine(value);
        _text.Dispatcher.Invoke(() => _text.AppendText(string.Format("{0} - {1}", DateTime.Now, value + Environment.NewLine)));
    }

    public override Encoding Encoding
    {
        get { return Encoding.UTF8; }
    }
}

这一切都很好,但是当我尝试从批处理文件输出回声结果时,我遇到了问题。我已经查看过很多关于这个主题的问题/答案,例如:View Output in a Batch (.bat) file from C# code但这些选项并不适用于我。 当调用WriteLineWrite函数的覆盖时,它只会挂起并且不会写任何内容。我该如何解决?

我的Process实施方式如下:

Process process = new Process();
process.OutputDataReceived += ReadOutput;
process.ErrorDataReceived += ReadErrorOutput;
process.EnableRaisingEvents = true;
//process.StartInfo = new ProcessStartInfo(@"cmd.exe", @"/c " + Path.Combine(Environment.CurrentDirectory, "BatchFile", "test.bat"))
process.StartInfo = new ProcessStartInfo(Path.Combine(Environment.CurrentDirectory, "BatchFile", "test2.bat"))
{
    UseShellExecute = false,
    Verb = "runas",
    RedirectStandardOutput = true,
    RedirectStandardError = true,
    CreateNoWindow = true,
    //WorkingDirectory = Path.Combine(Environment.CurrentDirectory, "BatchFile", "Information.bat"),
    WorkingDirectory = Path.Combine(Environment.CurrentDirectory, "BatchFile") + @"\",
};

process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();

process.WaitForExit();

输出重定向:

private void ReadOutput(object sender, DataReceivedEventArgs e)
{
    if (e.Data == null)
        return;

    Console.Write(e.Data);
}

private void ReadErrorOutput(object sender, DataReceivedEventArgs e)
{
    if (e.Data == null)
        return;

    Console.Write(e.Data);
}

目前批处理文件非常简单:

echo off
echo Finding Information
echo .......................
echo foo bar is cool
echo this day kinda sucks
echo .......................

echo All Processes Complete!

1 个答案:

答案 0 :(得分:1)

您的问题是您已经使UI线程陷入僵局。

您还没有共享一个完整的代码示例,以确定执行外部进程的代码的确切上下文,但根据您的问题描述,几乎可以肯定在UI线程中运行的某些代码中找到它,例如Button对象的Click事件处理程序,如下所示:

private void Button_Click(object sender, RoutedEventArgs e)
{
    Process process = new Process();
    process.OutputDataReceived += ReadOutput;
    process.ErrorDataReceived += ReadErrorOutput;
    process.EnableRaisingEvents = true;
    process.StartInfo = new ProcessStartInfo(Path.Combine(Environment.CurrentDirectory, "BatchFile", "test2.bat"))
    {
        UseShellExecute = false,
        Verb = "runas",
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        CreateNoWindow = true,
        WorkingDirectory = Path.Combine(Environment.CurrentDirectory, "BatchFile") + "\\",
    };

    process.Start();
    process.BeginErrorReadLine();
    process.BeginOutputReadLine();

    process.WaitForExit();
}

另一方面,当收到数据时,您尝试通过调用TextBox将控制转移到UI线程(以便可以安全地访问Dispatcher.Invoke()):

_text.Dispatcher.Invoke(...)

Invoke()方法只能在UI线程可用于接收消息时完成。但是你的其他代码在那里阻止了UI线程,因为它等待进程完成,防止其他任何事情发生。


最明显的解决方法是删除调用process.WaitForExit();。如果在执行该过程的方法中实际上没有剩余的代码可以执行,那么这是合适的。

但是,如果在代码中你没有共享,实际上有一些代码需要在进程完成时执行,你可以在不阻塞UI线程的情况下完成它。反过来做 的最明显的方法是为Process.Exited事件添加一个处理程序,当事件退出时会引发(当然)。

当然,该代码也可能需要在UI线程上执行。在这种情况下,您需要再次致电Dispatcher.Invoke()。事实上,这很好,但是代码确实开始变得有点笨拙和笨拙。另一种方法是将async / awaitExited事件相结合,以简化代码的外观。例如:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    Process process = new Process();
    TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

    process.OutputDataReceived += ReadOutput;
    process.ErrorDataReceived += ReadErrorOutput;
    process.Exited += (sender, e) => tcs.SetResult(true);
    process.EnableRaisingEvents = true;
    process.StartInfo = new ProcessStartInfo(Path.Combine(Environment.CurrentDirectory, "BatchFile", "test2.bat"))
    {
        UseShellExecute = false,
        Verb = "runas",
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        CreateNoWindow = true,
        WorkingDirectory = Path.Combine(Environment.CurrentDirectory, "BatchFile") + "\\",
    };

    process.Start();
    process.BeginErrorReadLine();
    process.BeginOutputReadLine();

    bool result = await tcs.Task;

    // Do your additional post-process work here
}

这将有效地允许该方法暂停执行并等待进程退出,而不会实际导致线程本身被阻止。该方法返回await表达式,然后在任务完成后稍后恢复执行该方法。