问题非常简单:我需要更新WPF应用程序的进度,同时处理耗时的计算。在我的尝试中,我做了一些谷歌搜索,最后基于此解决方案的第一个代码片段:How to update UI from another thread running in another class。这是我的代码:
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
的建议,但在这种情况下,进度不会更新,直到该过程完成工作。我想这个问题与线程之间的切换有关,但是无法得到准确的点。
答案 0 :(得分:1)
这是一个经典的&#34;调用死锁&#34;场景。 Stack Overflow有许多解决Winforms问题的现有问题,但我只能在WPF上下文中找到一个相关的问题(Deadlock when thread uses dispatcher and the main thread is waiting for thread to finish),但这个问题不是完全相同的问题,或者至少是没有答案(还)会直接解决你的问题,问题和答案本身也很差,我觉得你的问题需要一个新的答案。所以......
基本问题是您已阻止等待进程完成的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
。
答案 1 :(得分:1)
我按原样运行你的代码,并注释掉了这个:
while (thr.IsAlive == true)
{
Thread.Sleep(50);
}
一切都按预期工作。
/ 用户评论后编辑 /
要获得处理完成的通知,请执行以下更改:
一个。您的WorkerClass中的public event EventHandler ProgressCompleted;
。
湾
if (ProgressCompleted != null)
ProgressCompleted(this, new EventArgs());
在您完成Process()方法之后。
℃。在创建线程之前的BtnStart_Click中。
wItem.ProgressCompleted += (s1, eArgs1) =>
{
MessageBox.Show("Job is done!");
};