greenoldman
greenoldman

Reputation: 21062

How to start a thread to keep GUI refreshed?

I have window with button which triggers lengthy processing. I put processing in a separate thread, but -- to my surprise -- it makes GUI frozen anyway. No control is refreshed, I cannot even move the window.

So the question is how to start the thread, so it won't interfere with GUI, i.e. so the GUI would always be up to date (while processing I change the data, and GUI displays some pieces of it)?

That is how I start thread currectly:

        var thread = new Thread(doLearn);
        thread.IsBackground = true;
        thread.Start();

Edit 1

Jon:

  1. I don't use any locks at all
  2. No Join calling
  3. The UI thread is left alone -- it simply sits there

The processing is a big loop with math operations, not even allocating memory, on UI side I have controls with binding (WPF) to data, like the number of current iteration of the main loop. It should be refreshed each time the main loop "ticks". The counter of the loop is a property which triggers OnPropertyChanged with each change (classic WPF binding).

Edit 2 -- Almost there!

Ok, so Jon hit the nail at the head (who is surprises? ;-D) -- thank you! The problem comes from changing the Counter. When I used instead the Counter, local counter the GUI was refreshed -- I mean I could move windows, but... I couldn't see display of the Counter.

What I have here -- a WPF GUI, with such data-binding

<TextBlock Text="{Binding Path=Counter"/>

and I have Counter property of course which on each change sends event PropertyChanged. One of the listeners is for sure GUI.

So, Jon answer is valid "the answer", but from good design POV not exactly, because if GUI part has to pull up the info about Counter and update the display every (let's say) 3 seconds, why would anyone use data binding? For me such approach invalidates data binding idea.

I could, theoretically, pass to the processing thread the GUI dispatcher, and do all the sending in GUI thread, and it could work (I didn't try it) but it would mean tight coupling of non-GUI part and GUI part.

So far, I have no idea how to do it "right" way. The best guess so far is to create TimerDispatcher but not at GUI side but inside the processing library, and update Counter value immediately but do all the sending from time to time (I didn't try it yet though).

Small remark: I have more properties binded actually, like IsRunning, which is changed at the beginning and at the end of processing. And those changes DO affect the display correctly -- but the Counter change triggers around 3000 notifications in 3-4 seconds. So it looks like jamming problem. I did another test -- I killed the data binding partially, so notifications were sent, but GUI was not "receiving" them -- but was listening to them. In such case the GUI was also frozen.

So, I am still listening to all advices -- thank you advance for sharing.

Edit 3

The saga continues here:

How to do the processing and keep GUI refreshed using databinding?

Upvotes: 2

Views: 1344

Answers (4)

PawanS
PawanS

Reputation: 7193

I faced the same situation, and solved it by two ways...

  1. Use the thread in other class and invoke it in ur main application by creating Thread, either in its constructor OR in any method.

  2. if u want do the it in same class, then create a Thread that call your function, and that function should invoke the Delegate.

See the examples:

     public partial class Form1 : Form
    {
        private delegate void TickerDelegate();
        TickerDelegate tickerDelegate1;

        public Form1()
        {
            InitializeComponent();
        }
   //first solution
   // This button event call other class having Thread

       private void button1_Click(object sender, EventArgs e) 
        {
            f = new FormFileUpdate("Auto File Updater", this);
            f.Visible = true;
            this.Visible = false;         
        }

        // Second Solution
        private void BtnWatch_Click(object sender, EventArgs e)
        {
            tickerDelegate1 = new TickerDelegate(SetLeftTicker);
            Thread th = new Thread(new ThreadStart(DigitalTimer));
            th.IsBackground = true;
            th.Start();
         }

        private void SetLeftTicker()
        {
            label2.Text=DateTime.Now.ToLongTimeString();
        }


        public void DigitalTimer()
        {
            while (true)
            {
                label2.BeginInvoke(tickerDelegate1, new object[] {});
                Thread.Sleep(1000);
            }
        }
    }

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1500505

It should be fine as it is. Things which may be freezing your UI:

  • Are you locking within the UI thread, and locking on the same lock in your other thread?
  • Are you calling Join on the thread from your UI thread?
  • Are you doing some other heavy work in the UI thread?

If you could come up with a short but complete program which shows the problem, I'm sure we could help to fix it... but it certainly should be okay.

EDIT: Okay, now you've added this:

The counter of the loop is a property which triggers OnPropertyChanged with each change (classic WPF binding).

So you're updating the property from the non-UI thread? I would expect that to cause problems, because it will trigger UI changes from the wrong thread.

I suggest you take an approach such as:

  • Periodically update the counter via Dispatcher.BeginInvoke
  • Have the "UI counter" and the "worker counter" - and copy the value from the "worker counter" to the "UI counter" in the UI thread via a DispatcherTimer, essentially polling it.

Upvotes: 3

TomTom
TomTom

Reputation: 62093

I put processing in a separate thread, but -- to my surprise -- it makes GUI frozen anyway.

I really hate to tell you, but then you did NOT put it into a separate thread. That simlpe.

There was a poster here that had a similar issue some time ago and through a mistake in his invoking code he basically had all processing before the thread started, with the thread jsut returning the result.

Upvotes: 0

Reddog
Reddog

Reputation: 15579

There are numerous methods to run functions off the UI thread, but the easiest and generally most suitable is to look at the BackgroundWorker component. Many decent tutorials can be found. For example, here.

Upvotes: 3

Related Questions