1

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.

Community
  • 1
  • 1
Vasiliy
  • 948
  • 10
  • 14
  • Possible duplicate of [How to update UI from another thread](http://stackoverflow.com/questions/9602567/). – Dour High Arch Oct 17 '15 at 21:38
  • @DourHighArch: this question clearly shows the OP already understands how to use `Dispatcher.Invoke()`. He is having a different (albeit common) problem with it. This question is not a duplicate of your proposed duplicate. It might be a duplicate of some other question, but I did look and couldn't find one that was for WPF (there are a number for Winforms) and which addresses this question directly and in an easily comprehended way. – Peter Duniho Oct 18 '15 at 03:08
  • @DourHighArch, yes. I saw the thread and wrote my code based on the first snippet from the solution reply. As Peter Duniho mentioned, the question is more about why it may not work. I spent two days on the issue without results, need some kind of point where to dig. – Vasiliy Oct 18 '15 at 08:16

2 Answers2

1

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.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
1

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!");
            };
AnjumSKhan
  • 9,647
  • 1
  • 26
  • 38
  • AnjumSKhan, thanks for your reply. Indeed it works, but the "Job is done!" message appears right after button was clicked rather then after thread thr finishes. I need it to appear after thread thr finishes its work, as there will be some calculations based on wItem.Process() results. – Vasiliy Oct 18 '15 at 07:59
  • AnjumSKhan, the updated version works for me. But it requires one more event in Worker class, and makes code more bulky in case if I need to implement more complex logic along with "Job is done" message. So in this particular case I vote for TPL-based solution. Anyway your idea is interesting and applicable for particular use cases, I've put it in the ideas treasury. Thank you! – Vasiliy Oct 19 '15 at 06:54
  • @Vasiliy Count number of events in any WPF control. – AnjumSKhan Oct 20 '15 at 03:37