更新 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
,但在这种情况下,在流程完成工作之前,进度不会更新。我想这个问题与线程之间的切换有关,但无法获得确切的要点。
这是一个经典的"调用死锁"方案。堆栈溢出有许多现有的问题来解决Winforms的这个问题,但我只能在WPF上下文中找到一个相关的问题(线程使用调度程序并且主线程等待线程完成时出现死锁(,但那个问题不完全相同,或者至少没有答案(还(可以直接解决您的问题, 问题和答案本身的形成很差,我觉得你的问题需要一个新的答案。所以。。。
基本问题是您已阻止 UI 线程等待进程完成。你永远不应该这样做。切勿出于任何原因阻止 UI 线程。很明显,如果您在 UI 线程中运行的代码中出于任何原因等待,则 UI 本身无法响应用户输入或执行任何类型的屏幕刷新。
有很多可能的方法可以解决这个问题,但目前处理这个问题的习惯用语是使用 async
和 await
,以及运行进程的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
方法声明为返回Task
或Task<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!");
};