更新 C# WPF 应用程序耗时任务的进度

本文关键字:任务 WPF 应用程序 更新 | 更新日期: 2023-09-27 18:18:05

这个问题很简单:我需要在处理耗时的计算时更新 WPF 应用程序的进度。在我的尝试中,我进行了一些谷歌搜索,最后基于此解决方案中的第一个代码片段:如何从另一个类中运行的另一个线程更新 UI。这是我的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
namespace ThreadTest
{
    public class WorkerClass
    {
        public int currentIteration;
        public int maxIterations = 100;
        public event EventHandler ProgressUpdate;
        public void Process()
        {
            this.currentIteration = 0;
            while (currentIteration < maxIterations)
            {
                if (ProgressUpdate != null)
                    ProgressUpdate(this, new EventArgs());
                currentIteration++;
                Thread.Sleep(100); // some time-consuming activity takes place here
            }
        }
    }
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            WorkerClass wItem = new WorkerClass();
            wItem.ProgressUpdate += (s, eArg) => {
                Dispatcher.BeginInvoke((Action)delegate() { txtProgress.Text = wItem.currentIteration.ToString(); });
            };
            Thread thr = new Thread(new ThreadStart(wItem.Process));
            thr.Start();
            // MessageBox.Show("Job started...");
            while (thr.IsAlive == true)
            {
                Thread.Sleep(50); 
            }
            MessageBox.Show("Job is done!");
        }
    }
}

问题是,如果我使用 Dispatcher.Invoke ,那么工作线程 (thr( 在第一个周期通过后进入 WaitSleepJoin 状态并且不会恢复,因此整个应用程序都会冻结。我已经用谷歌搜索了几个建议来改用Dispatcher.BeginInvoke,但在这种情况下,在流程完成工作之前,进度不会更新。我想这个问题与线程之间的切换有关,但无法获得确切的要点。

更新 C# WPF 应用程序耗时任务的进度

这是一个经典的"调用死锁"方案。堆栈溢出有许多现有的问题来解决Winforms的这个问题,但我只能在WPF上下文中找到一个相关的问题(线程使用调度程序并且主线程等待线程完成时出现死锁(,但那个问题不完全相同,或者至少没有答案(还(可以直接解决您的问题, 问题和答案本身的形成很差,我觉得你的问题需要一个新的答案。所以。。。


基本问题是您已阻止 UI 线程等待进程完成。你永远不应该这样做。切勿出于任何原因阻止 UI 线程。很明显,如果您在 UI 线程中运行的代码中出于任何原因等待,则 UI 本身无法响应用户输入或执行任何类型的屏幕刷新。

有很多可能的方法可以解决这个问题,但目前处理这个问题的习惯用语是使用 asyncawait ,以及运行进程的Task。例如:

private async void btnStart_Click(object sender, RoutedEventArgs e)
{
    WorkerClass wItem = new WorkerClass();
    wItem.ProgressUpdate += (s, eArg) => {
        Dispatcher.BeginInvoke((Action)delegate() { txtProgress.Text = wItem.currentIteration.ToString(); });
    };
    Task task = Task.Run(wItem.Process);
    // MessageBox.Show("Job started...");
    await task;
    MessageBox.Show("Job is done!");
}

请注意,虽然上面声明async方法为 void ,但这是规则的例外。也就是说,通常应该始终将async方法声明为返回TaskTask<T>。例外情况是这样的情况,其中该方法是一个事件处理程序,因此需要匹配现有签名,其中必须声明该方法以返回void

我按原样运行了您的代码,并注释掉了以下内容:

while (thr.IsAlive == true)
            {
                Thread.Sleep(50); 
            }

一切都按预期工作。

/

用户评论后编辑/

要获得处理完成的通知,请执行以下更改:

一个。 public event EventHandler ProgressCompleted;在你的工人类中。

二.

if (ProgressCompleted != null)
       ProgressCompleted(this, new EventArgs());

在 Process(( 方法中完成之后。

三. 在创建线程之前BtnStart_Click。

wItem.ProgressCompleted += (s1, eArgs1) =>
            {
                MessageBox.Show("Job is done!");
            };