【问题标题】:Redirecting batch file echo commands to TextBox hangs on Dispatcher.Invoke call将批处理文件回显命令重定向到 TextBox 挂起 Dispatcher.Invoke 调用
【发布时间】:2015-11-03 19:34:14
【问题描述】:

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

目前我已对其进行了设置,以便 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!

【问题讨论】:

    标签: c# wpf batch-file


    【解决方案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();
    }
    

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

    _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 表达式处返回,然后在任务完成后继续执行该方法。

    【讨论】:

    • 进程后没有要执行的代码。这一切都在它自己的函数中,通过从附加到按钮Command 属性的xaml 中的RelayCommand 调用调用。感谢您的解决方案,这似乎解决了我的问题。
    猜你喜欢
    • 2020-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-28
    • 1970-01-01
    相关资源
    最近更新 更多