Windows Develop Bookmark and Share   
 index > Windows Forms General > How can I improve the status bar's refresh mechanism? It slows things down considerably!
 

How can I improve the status bar's refresh mechanism? It slows things down considerably!

First of all, let me describe the scenario.

A Windows Forms application that scans the majority of the Windows Registry. The scanning itself is done in a separate thread. I used Thread as opposed to WorkerThread. Why? I forgot WorkerThread existed until now, writing this question. :D Anyway, not a big deal. A separate thread takes a small list of selected hives and starts processing each hive, one by one, scanning each registry key recursively to cover the entire hive. My initial Windows Form looked something like this:

//////////////////////////////////////////////////////
// Keys scanned: XXX //
// TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT //
// TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT //
// TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT //
// TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT //
//////////////////////////////////////////////////////

where the border is a Groupbox control, "Keys scanned:" is a label control, XXX is another label control that updates the end user with the count of scanned regisistry keys, and the bunch of TTTT's represent a label that shows the key name being scanned.

With the above UI, I could scan some 92000 keys of HKCR in under a minute.

After that, I moved the Scanned key count to a status bar at the bottom, and inside the groupbox I started showing a "Keys Found" counter. Just by moving the scanned key count inside the progress bar, the process slowed down some 4 or five times (bringing the scan time way up to 4 to 5 minutes), AND it forced me to call Refresh() on both the keys found count label and the key name label. Without the Refresh() calls, the labels would only update sporadically.

If I comment out the line of code that updates the scanned key count (inside the status bar), the scanning process picks up at a much higher speed, and I no longer need the Refresh() calls.

All of this in .Net 2.0.

My question would be: Is there anything I can do about this? Is the status bar control any better in .Net 3.0 or 3.5?
MCP
webJose  Friday, September 18, 2009 3:58 PM
1) BeginInvoke causing locking up is usually a sign that you're trying to invoke far too many events into the message pump. Slowing down the updates fixes this. When you switch to BeginInvoke, your method is running so much faster that it's flooding the message pump completely.

2) Status Bar updates are slower than a label updates for quite a few reasons. The main thing is that a status bar update tends to cause a full refresh of the entire status bar, which i, and since it's a full control with a lot more detail than a simple label, the redraw is much slower. (BTW, if you want the status bar, switching to a ToolStripStatusLabel hosted inside of a StatusStrip will allow just the label to update in the redraw, and probably fix your complaint.)

My recommendation would be to switch to StatusStrip/ToolStripStatusLabel instead of StatusBar, and just try slowing down the updates, but using BeginInvoke. I think you'll be suprised - there will be a balance in finding the correct update speed, but even if you update at rate of 60/sec (any faster isn't going to draw on a normal 60 Hz monitor!), it will probably perform a LOT faster than your current code. Personally, I think UIs that draw numbers > 4x/second are not usable, since it's changing too fast to follow.




Reed Copsey, Jr. - http://reedcopsey.com
  • Marked As Answer bywebJose Saturday, September 19, 2009 3:49 AM
  •  
Reed Copsey, Jr.  Friday, September 18, 2009 6:11 PM
First of all, my apologies. I realized I gave misleading information. I was not using a status bar control, I was using a StatusStrip control all this time. Therefore, my complaint is against the refresh mechanism of the StatusStrip control and not any other status bar control out there.

This leaves Reed's suggestion #2 out of scope. So no gains there.

But the good news is that I took your advise and slowed updates a bit and I was truly impressed. I started to think of a way to slow this down to a set frequency (in Hz) dynamically (kind of an automatic control), but the frequency actually rises as I start cutting update cycles, up to a maximum, where further cutting updates start decreasing the frequency. Furthermore, it will be different on each and every computer that is run because of the different hardware, configurations, and running applications. So it is a pain for my current purposes. So I decided to do a fixed cut as opposed to my original dynamic cut. I just don't feel like deriving an equation and then deriving it against the cut rate to find its maximum so I could obtain my initial cut rate, and then dynamically start adjusting the number. Besides, as you'll see, the program will be done much sooner than originally seen.

So, the results summarized (running in my home computer, Q6600 @ 3.12 GHz, 8GB RAM & Windows 7 Ultimate 64-bit):

The original code:

  • Used Invoke().
  • Updated the UI every pass (92916 passes for HKCR alone).

The new code:

  • Uses BeginInvoke().
  • Updates the UI every 81 cycles (5509 passes for HKCR).

The old code ran a pass on HKCR in 2:09; the new code runs in under 9 seconds.

Worth noting: The switch from Invoke() to BeginInvoke() reduces the time in more than half (some 60%).

The watch outs.

Switching from Invoke() to BeginInvoke() is tricky. It will make the UI sluggish if you don't calibrate things right. In a couple of test runs with an update rate of 24 cycles, the Stop button would respond around 1.5 seconds behind the click. The refresh rate was just too high. But things got better with a higher cut in updates. When I hit 400Hz, the Stop button would respond immediately. It works immediately at higher frequencies too, as seen in my "final" value of 81 dropped updates (612Hz), but my guess is that this refresh rate might be too much on lesser hardware, such as a laptop with a lot of programs running.

Label.Refresh() is hardly any impact in the performance. I recommend using it always because it ensures the update of the UI.

Do NOT set the number of dropped updates to a "nice round value". Meaning: Do not set it to 10, or 20, or 100, or 1000. Why? The total counter will leave those zeroes intact. Example: If set to 100, the total scanned keys count will be seen as increasing from 0 to 100, to 200, to 300, etc. Looks "artificial", lacking of a better word to describe it.

So, without having an answer to the original question (what can be done to improve the refresh rate of the StatusStrip control?), the "workaround" proposed by Reed is THE BOMB. :-)

I would imagine that it has to be recognized as a best practice in programming. I should have known this. Shame on me!

Just in case it isn't clear: Reed, thanks a lot for your input. And you were right on with my BeginInvoke() freezing the UI.

I leave a bit of code here to complete this documentation:

        private void UpdateProgress(string keyName, bool isWRP)
        {
            _foundCount += isWRP ? 1U : 0;
            _count++;
            _lastKey = keyName;
            if (++_raiseProgressCount >= 81)
            {
                _updatesCount++;
                UpdateProgressFinal(keyName);
                _raiseProgressCount = 0;
            }
        }
        private void UpdateProgressFinal(string keyName)
        {
            lblFoundKeyCount.Text = _foundCount.ToString();
            lblFoundKeyCount.Refresh();
            lblKeyCount.Text = String.Format("Scanned:  {0}", _count);
            lblKeyName.Text = keyName;
            lblKeyName.Refresh();
        }


MCP
  • Marked As Answer bywebJose Saturday, September 19, 2009 3:50 AM
  •  
webJose  Saturday, September 19, 2009 3:42 AM
It sounds like your main issue is actually the UI updates -

I would recommend slowing down the speed at which your UI updates. If, for example, you only updated the UI once per second (ie: just keep a last updated time, and only invoke to the UI if it's been a full second, or a half second, etc- or, alternatively, just update once every 1000 keys), this will probably run in a fraction of the time you are running in now, AND be easier to read since the UI won't be trying to refresh continually.

Also, make sure to use BeginInvoke and not Invoke to call back to the UI, so your algorithm isn't waiting on the UI redraw before it continues on processing.

If you do those two things, the UI's responsiveness will improve, the label will be more legible (it's very difficult to read something changing hundreds of times / second), and your algorithm will most likely be dramatically faster.

Reed Copsey, Jr. - http://reedcopsey.com
Reed Copsey, Jr.  Friday, September 18, 2009 4:13 PM
Hi Reed. Funny thing that you mentioned BeginInvoke. I did try it but my application stops responding. If I switch to Invoke() then everything works. Any ideas on this?

As for the original problem, I appreciate the advise, but that is not really the question here. I am aware of the troubles that Invoke() + updating every single time carry. I understand that slowing down the update frequency will improve the times. The issue at hand is: Why, using the exact same update frequency, a status bar-less user interface performs much faster than a user interface with a status bar? The status bar labels do not provide a Refresh() method, so clearly a change in its Text property triggers a refresh. This refresh seems to be inefficient, though because the slowdown it produces is extreme. My question is a bit academic and a bit a request for suggestions to improve the status bar.

If you saw the application the application performing updates every time it runs, and then run it updating at a lower frequency, you'd agree (I think) that it looks strange, as if it were struggling to complete the job somehow. This is why I am initially reluctant to slow down the updates. Besides, other UI controls (like the label) perform much better. I'd rather remove the status bar before slowing down the update frequency.

You thoughts and comments are most welcome, of course. Especially with the BeginInvoke() function.

Thanks!
MCP
webJose  Friday, September 18, 2009 6:02 PM
1) BeginInvoke causing locking up is usually a sign that you're trying to invoke far too many events into the message pump. Slowing down the updates fixes this. When you switch to BeginInvoke, your method is running so much faster that it's flooding the message pump completely.

2) Status Bar updates are slower than a label updates for quite a few reasons. The main thing is that a status bar update tends to cause a full refresh of the entire status bar, which i, and since it's a full control with a lot more detail than a simple label, the redraw is much slower. (BTW, if you want the status bar, switching to a ToolStripStatusLabel hosted inside of a StatusStrip will allow just the label to update in the redraw, and probably fix your complaint.)

My recommendation would be to switch to StatusStrip/ToolStripStatusLabel instead of StatusBar, and just try slowing down the updates, but using BeginInvoke. I think you'll be suprised - there will be a balance in finding the correct update speed, but even if you update at rate of 60/sec (any faster isn't going to draw on a normal 60 Hz monitor!), it will probably perform a LOT faster than your current code. Personally, I think UIs that draw numbers > 4x/second are not usable, since it's changing too fast to follow.




Reed Copsey, Jr. - http://reedcopsey.com
  • Marked As Answer bywebJose Saturday, September 19, 2009 3:49 AM
  •  
Reed Copsey, Jr.  Friday, September 18, 2009 6:11 PM
Yup, tune your program to what the human eye can perceive. When you watch a movie in the cinema, you are looking at 24 updates per second. Anything beyond that just slows down your program but makes no perceivable difference to the user. When you get close to 1000 updates per second, the UI will go dead as no paint events manage to get through anymore.

Hans Passant.
nobugz  Friday, September 18, 2009 6:21 PM
First of all, my apologies. I realized I gave misleading information. I was not using a status bar control, I was using a StatusStrip control all this time. Therefore, my complaint is against the refresh mechanism of the StatusStrip control and not any other status bar control out there.

This leaves Reed's suggestion #2 out of scope. So no gains there.

But the good news is that I took your advise and slowed updates a bit and I was truly impressed. I started to think of a way to slow this down to a set frequency (in Hz) dynamically (kind of an automatic control), but the frequency actually rises as I start cutting update cycles, up to a maximum, where further cutting updates start decreasing the frequency. Furthermore, it will be different on each and every computer that is run because of the different hardware, configurations, and running applications. So it is a pain for my current purposes. So I decided to do a fixed cut as opposed to my original dynamic cut. I just don't feel like deriving an equation and then deriving it against the cut rate to find its maximum so I could obtain my initial cut rate, and then dynamically start adjusting the number. Besides, as you'll see, the program will be done much sooner than originally seen.

So, the results summarized (running in my home computer, Q6600 @ 3.12 GHz, 8GB RAM & Windows 7 Ultimate 64-bit):

The original code:

  • Used Invoke().
  • Updated the UI every pass (92916 passes for HKCR alone).

The new code:

  • Uses BeginInvoke().
  • Updates the UI every 81 cycles (5509 passes for HKCR).

The old code ran a pass on HKCR in 2:09; the new code runs in under 9 seconds.

Worth noting: The switch from Invoke() to BeginInvoke() reduces the time in more than half (some 60%).

The watch outs.

Switching from Invoke() to BeginInvoke() is tricky. It will make the UI sluggish if you don't calibrate things right. In a couple of test runs with an update rate of 24 cycles, the Stop button would respond around 1.5 seconds behind the click. The refresh rate was just too high. But things got better with a higher cut in updates. When I hit 400Hz, the Stop button would respond immediately. It works immediately at higher frequencies too, as seen in my "final" value of 81 dropped updates (612Hz), but my guess is that this refresh rate might be too much on lesser hardware, such as a laptop with a lot of programs running.

Label.Refresh() is hardly any impact in the performance. I recommend using it always because it ensures the update of the UI.

Do NOT set the number of dropped updates to a "nice round value". Meaning: Do not set it to 10, or 20, or 100, or 1000. Why? The total counter will leave those zeroes intact. Example: If set to 100, the total scanned keys count will be seen as increasing from 0 to 100, to 200, to 300, etc. Looks "artificial", lacking of a better word to describe it.

So, without having an answer to the original question (what can be done to improve the refresh rate of the StatusStrip control?), the "workaround" proposed by Reed is THE BOMB. :-)

I would imagine that it has to be recognized as a best practice in programming. I should have known this. Shame on me!

Just in case it isn't clear: Reed, thanks a lot for your input. And you were right on with my BeginInvoke() freezing the UI.

I leave a bit of code here to complete this documentation:

        private void UpdateProgress(string keyName, bool isWRP)
        {
            _foundCount += isWRP ? 1U : 0;
            _count++;
            _lastKey = keyName;
            if (++_raiseProgressCount >= 81)
            {
                _updatesCount++;
                UpdateProgressFinal(keyName);
                _raiseProgressCount = 0;
            }
        }
        private void UpdateProgressFinal(string keyName)
        {
            lblFoundKeyCount.Text = _foundCount.ToString();
            lblFoundKeyCount.Refresh();
            lblKeyCount.Text = String.Format("Scanned:  {0}", _count);
            lblKeyName.Text = keyName;
            lblKeyName.Refresh();
        }


MCP
  • Marked As Answer bywebJose Saturday, September 19, 2009 3:50 AM
  •  
webJose  Saturday, September 19, 2009 3:42 AM

You can use google to search for other answers

Custom Search

More Threads

• toolstrip jumps out of toolstripcontainer
• How to speed up the Forms Load event
• Add rows into a usedrange of an excel worksheet
• zooming in on images at run time
• Embed Office Word into Windows Form
• 'global' key events ?
• Splitting form horizontally in 4 panels
• Will there be a Data Repeater control released in C# for Windows Forms?
• Translating From C# to VB
• MS chat