【问题标题】:How do I update a WPF control based on console output from a Process?如何根据进程的控制台输出更新 WPF 控件?
【发布时间】:2016-06-21 19:38:28
【问题描述】:

我有一个简单的控制台应用程序,可以向标准输出报告进度。例如:

static void Main(string[] args)
{
    for (int i = 0; i <= 100; i += 10)
    {
        Console.WriteLine(i);
        System.Threading.Thread.Sleep(1000);
    }
}

产生这个输出:

0
10
20
30
40
50
60
70
80
90
100

我想通过 .NET Process 调用此应用程序,并异步更新 WPF 控件(特别是 ProgressBar):

private void Run()
{
    Process process = new Process();
    process.StartInfo.FileName = "ConsoleApplication1.exe";
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.CreateNoWindow = true;
    process.StartInfo.RedirectStandardOutput = true;
    process.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
    {
        if (e.Data != null)
        {
            Console.WriteLine(e.Data);
            ProgressBar.Value = double.Parse(e.Data);
        }
    });
    process.Start();
    process.BeginOutputReadLine();
    process.WaitForExit();
    process.Close();
}

当我运行它时,它会毫无问题地写入控制台,但可以理解的是抱怨试图从不同的线程访问 ProgressBar,所以我用调用来包装它:

Application.Current.Dispatcher.Invoke(() =>
{
    ProgressBar.Value = double.Parse(e.Data);
});

但是,它在调用时会冻结。大概是发生了死锁,虽然我不明白为什么。

环顾四周,似乎 Process.SynchronizingObject 旨在解决这些情况,但我无法从 WPF 中看到如何使用它,因为控件没有 ISynchronizeInvoke 属性。

【问题讨论】:

  • 如果您在应用程序初始化时声明public System.Windows.Threading.Dispatcher dispatch = System.Windows.Threading.Dispatcher.CurrentDispatcher; 之类的内容并使用该调度程序,它会改变行为吗?
  • @pay:没有变化,不管我把dispatch放在MainWindow还是App
  • 嗯,所以即使在UI线程上创建了dispatcher,它还是死锁了?也许也可以从该事件处理程序的内部删除。
  • @pay:从事件处理程序内部删除什么?
  • 致电Invoke

标签: c# .net wpf process


【解决方案1】:

WaitForExit() 使当前线程等待,直到相关进程终止。应该在进程上调用所有其他方法之后调用它。为避免阻塞当前线程,请使用 Exited 事件。

来源:MSDN

因此,您不应该使用process.WaitForExit(),因为它会使 UI 线程等待进程退出。如果只想在进程退出后执行代码,处理process.Exited事件。

另外,process.ErrorDataReceivedprocess.OutputDataReceivedprocess.Exited 在另一个线程中引发。为了让它们与您在 UI 线程(即您的 ProgressBar)中的控件进行交互,您应该以这种方式处理事件:

private void process_OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        // do additional processing with e.Data if this is what you desire
        ProgressBar.Dispatcher.Invoke(new Action(()=> { ProgressBar.Value=double.Parse(e.Data); }));
    }

使用ProgressBar.Dispatcher 可确保调用 ProgressBar 所在线程的调度程序。 请注意,我没有运行此代码,它是根据我使用的内容改编的。

请原谅任何错误,第一次发布。 另外,我很抱歉恢复了这么旧的帖子,但这是来自谷歌的结果,我希望我能帮助别人。

【讨论】:

    【解决方案2】:

    建议将您的ProgressBar 绑定到触发change notifications 的对象并更新该对象的属性。这样您就可以避免直接使用Dispatcher 并访问视图(无论如何您可能都不应该这样做)。

    private double _Progress = 0;
    public double Progress
    {
        get { return _Progress; }
        set
        {
            if (value != _Progress)
            {
                _Progress = value;
                OnPropertyChanged("Progress");
            }
        }
    }
    

    // Dummy updates, 10% every second
    new Thread(p =>
    {
        for (int i = 0; i < 10; i++)
        {
            Progress += 10;
            Thread.Sleep(1000);
        }
    }).Start();
    
    <ProgressBar Value="{Binding Progress}" Height="20px" HorizontalAlignment="Stretch"/>
    

    【讨论】:

    • 你能展示一下在这种情况下如何工作的代码吗?
    • 这完全是微不足道的,我相信你可以做到。
    • @CraigW:认真的吗?
    • 这是一个很好的数据绑定教科书示例,但与我的场景无关(Process 的控制台输出)。
    • 你已经有了读取输出的代码,你的代码中唯一改变的是你直接分配了一个属性而不是ProgressBar.Value...
    猜你喜欢
    • 1970-01-01
    • 2018-08-08
    • 1970-01-01
    • 2018-10-30
    • 2014-03-13
    • 1970-01-01
    • 1970-01-01
    • 2018-10-18
    • 2017-02-07
    相关资源
    最近更新 更多