ProgressBar with extremely busy message thread


#1

So I’m being dense here, but how do I make a ProgressBar paint itself if the message thread is under heavy load.

Let’s say I have an app blocking task, for arguments sake, reading in a large block of data. I do something like:


void readInBigStuff()
{
  double progress = 0;
  ProgressBar pb(progress);

  // ---- add pb to desktop, set its size, visibility whatever -- //

  while (stuffToRead)
  {
     -- read a block of data in -- 
     progress = some arbitrarily calculated progress percent.
  }

}

ProgressBar manages its own thread, so I can launch it from the message thread before doing something massive, right?

What I’m getting is that the bar never shows. Its repaints are being surpressed in the same way that normal repaints would be when the system is loaded. If I don’t delete the progressbar after the busy work is done, it appears as soon as the work is done, and does a passable job of pretending that the entire process took under a second.

I can’t move the work onto another thread, as in this case the heavy work is a LUA script and it is far safer if external scripts block the entire application for the duration of their operation.

What am I missing?


#2

Well no, a ProgressBar is just a normal UI comp, but you could use a ThreadWithProgressWindow object to do it automatically.

Or if you’re doing all the processing on your message thread, you could periodically make it call MessageManager::deliverAllPendingMessages, which will let things repaint if they need to.


#3

I have a similar situation with a busy main thread and a progress bar.

One thing I noticed with the ProgressBar is that the timer callback function limits increasing values to a maximum 2% jump:

if (currentValue < newProgress && newProgress >= 0 && newProgress < 1.0 && currentValue >= 0 && newProgress < 1.0) { newProgress = jmin (currentValue + 0.02, newProgress); }

This gives nice ballistics, but it also makes the bar display an incorrect value if the timer doesn’t get to run often enough. In my case, I run through a loop about 15 times, and call MessageManager::getInstance()->dispatchPendingMessages() once each time through. Most of the execution time in the loop comes from a large file copy, so I can’t call dispatchPendingMessages() more frequently.

By the last iteration, my progress bar shows I’m only 30% done since the timer callback only got to run 15 times. It zips to 100% after my loop finishes, but it doesn’t give a useful display while the loop is running.

I modified the ProgressBar Component to add an option to turn ballistics off - pretty simple. There are other ways (like maybe the ThreadWithProgressWindow ?) to work around it of course, but this seemed easiest for my application, unless I’ve missed something.


#4

Ok… Well, perhaps a good way to handle this is just to make the progressbar a bit smarter - if it checks how long it’s taken between updates, it can just avoid doing the ballistics if the timers aren’t happening often enough.


#5

Yes, that’s a better idea. That way you don’t have to worry about fiddling with a setting depending on how fast the computer is, how heavy the workload is, etc. I think I’ll give that a try soon as I get a chance here.


#6

I’ve already done it - will check in a fix shortly…


#7

That’s great, thanks!


#8

I’m having similar issues. I’m making an app which reads an audio file and processes it out to a new file.

I’m using a ThreadWithProgressWindow and doing the reader/writer stuff in its thread. I have a value going from 0.0 to 1.0 in the process loop which I’m using to setProgress() but it only gets to about 30% then jumps to 100% at the end. If I sleep for a couple of ms in the loop it works fine but I don’t want to slow down the process just for ths reason!

I’ve tried using Thread::yield() rather than sleeping but that doesn’t seem to be enough.


#9

Sounds odd that the message thread would be blocked for more than a second, even if your background thread is hammering the cpu… Maybe try lowering the priority of your background thread slightly?


#10

Yes sorry, I should have said. I’ve already set the priority of the ThreadWithProgressWindow thread to zero but that made little/no difference. I’ve tried updating less frequently thinking this might fool the ballistics but that doesn’t do anything either (e.g., every 1 or 5 % change).

Not sure it makes much difference but I’m doing some FFT stuff (using FFTW at present) in the loop (it’s a phase vocode time stretch) so I probably am hammering the CPU.


#11

Are you 100% sure you’re actually updating the progress numbers correctly? The thing to check would be that if the progress isn’t moving but the dialog box is still responsive to the mouse, then you mustn’t be updating it correctly.


#12

I’m 99% sure!

I’ve posted the value I’m sending to setProgress() in the console and it goes from 0.0 to 0.999999 in smallish steps.


#13

I just can’t see how a background thread could block the UI thread from updating, no matter how hard it’s running.

Maybe there’s something else going wrong… Is the UI responsive when it does this?


#14

Yes I can’t see why either. The UI is responsive, - I can move the progress window around quite smoothly while it’s processing. Here is the run() function:

[code]void PhaseVocoderTask::run()
{
if(audioFormatReader && audioFormatWriter && numChannels)
{
const double timeScale = phaseVocoderComponent->getComponentPropertyDouble(T(“timeScale”), false, 1.0);

	const int N = 8192;             // Frame-size
	const int Os = 8;               // Synthesis hop-factor
	const int Rs = N/Os;            // Synthesis hop-size (derived)
	const int Ra = Rs / timeScale;  // Analysis hop-size
	
	AudioSampleBuffer audioSampleBuffer(numChannels, N);
	PhaseVocoder phaseVocoder(audioSampleBuffer, Os);
	
	const int length = audioFormatReader->lengthInSamples;
	for(int readerStartSample = 0; readerStartSample < length; readerStartSample += Ra)
	{
		audioSampleBuffer.clear();
		audioSampleBuffer.readFromAudioReader(audioFormatReader, 0, N, readerStartSample, true, true);
		phaseVocoder.process(Ra);
		audioSampleBuffer.writeToAudioWriter(audioFormatWriter, 0, Rs);
		
		double progress = readerStartSample / (double)length;
		setProgress(progress);
		printf("progress=%f\n", progress);
	}
	
	setProgress(1.0);
	setStatusMessage(T("Done"));
	
	Thread::sleep(500);
	
	cleanup();

}

}
[/code]

It should be reasonably obvious what’s going on here except for the PhaseVocoder class which keeps a reference to the AudioSampleBuffer and processes data in its buffer in place. It behaves similarly with the phaseVocoder.process(Ra) line removed anyway.


#15

This is really bothering me… If it’s responsive, then the progressbar must be messing up, but I just can’t see any bugs in the code!

How long does the whole process take? If it’s quick (i.e. less than maybe 3-4 seconds), then perhaps it’s just the progressbar’s smoothing algorithm that’s going too slowly to keep up with the actual position?


#16

Actually the longer it takes the more accuate it is (which is perhaps why some sleep in the loop helps).

The tests I’m doing are generally quick so yes it’s less that 3-4 seconds where theare are problems. But in the final app the user might be loading large sound files and doing complex processing which could take minutes to render. Or they might be loading short files and doing simple processing.

I suppose I’m trying to find a simple general solution. I could try to figure out in advance if the processing is going to be quick or slow but that would complicate things. I’d probably end up writing a different version of ThreadWithProgressWindow.

Perhaps something where the progress bar only appears if the processing is taking longer than a few seconds?


#17

Ah, I think I’ve figured it out. There’s a quirk in the way the progress bar works, which means its speed will depend on how often you change the progress… When I wrote it, my test code was sending infrequent updates, but for fast updates it all goes a bit too slowly. Will check in a tweak shortly!


#18