Reputation: 998
The question is pretty trivial: I need to update progress on WPF application while time-consuming calculation is processed. In my tries, I've made some googling and finally based on the first code snippet from this solution: How to update UI from another thread running in another class. And here's my code:
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!");
}
}
}
The issue is that if I use Dispatcher.Invoke
, than the working thread (thr) gets into WaitSleepJoin state after the first cycle pass and does not resume, therefore the entire application freezes. I've googled several suggestions to use Dispatcher.BeginInvoke
instead, but in this case the progress is not updated untill the process finishes the work. I guess the issue is related to switching between threads, but cannot get exact point.
Upvotes: 1
Views: 394
Reputation: 9827
I ran your code as it is, and commented out this :
while (thr.IsAlive == true)
{
Thread.Sleep(50);
}
Everything worked as expected.
/Edit after user comment/
To get notified of processing completion, do these changes :
a. public event EventHandler ProgressCompleted;
in your WorkerClass.
b.
if (ProgressCompleted != null)
ProgressCompleted(this, new EventArgs());
after your while finishes in Process() method.
c. In your BtnStart_Click before creating thread.
wItem.ProgressCompleted += (s1, eArgs1) =>
{
MessageBox.Show("Job is done!");
};
Upvotes: 1
Reputation: 70701
This is a classic "Invoke deadlock" scenario. Stack Overflow has a number of existing questions addressing this problem for Winforms, but I could only find one related question in the WPF context (Deadlock when thread uses dispatcher and the main thread is waiting for thread to finish), but that one isn't precisely the same question, or at least doesn't have answers (yet) that would directly address your question, and the question and answers themselves are poorly enough formed that I feel your question warrants a fresh answer. So…
The basic issue is that you have blocked your UI thread waiting on the process to finish. You should never do this. You should never block the UI thread for any reason. It should be obvious that if you wait for any reason in code running in the UI thread, then the UI itself cannot respond to user input or do any sort of screen refresh.
There are lots of possible ways to address this, but the current idiom for dealing with this would be to use async
and await
, along with Task
to run your process. For example:
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!");
}
Note that while the above declares the async
method as void
, this is the exception to the rule. That is, normally one should always declare async
methods as returning Task
or Task<T>
. The exception is situations such as this, where the method is an event handler and so is required to match an existing signature where the method must be declared to return void
.
Upvotes: 1