Recent

Author Topic: File operations blocking UI update  (Read 3036 times)

atlatl

  • New Member
  • *
  • Posts: 11
File operations blocking UI update
« on: January 13, 2025, 10:09:54 pm »
Hello. I've run into an issue with a static text control and a progress bar not updating until after a file read occurs, even though the static text caption is set before the the file is even opened.

Code: Pascal  [Select][+][-]
  1. PROCEDURE TForm1.Button1Click(Sender: TObject);
  2.  
  3.   VAR
  4.     FilePath: String;
  5.     FileRef: File;
  6.     i: Integer;
  7.     FileData: Longword;
  8.  
  9.   BEGIN
  10.     IF Form1.SelectDirectoryDialog1.Execute
  11.     THEN FilePath:= Form1.SelectDirectoryDialog1.FileName + PathDelim + 'testfile'
  12.     ELSE EXIT;
  13.     Form1.StaticText1.Caption:= FilePath;
  14.     AssignFile(FileRef, FilePath);
  15.     Reset(FileRef, 4);
  16.     Seek(FileRef, 0);
  17.     Form1.StaticText2.Caption:= 'Reading...';
  18.     FOR i:= 1 to 100000 DO
  19.       BEGIN
  20.         Form1.ProgressBar1.Position:= i;
  21.         BlockRead(FileRef, FileData, 1);
  22.         Form1.StaticText2.Caption:= IntToStr(i);
  23.       END;
  24.     CloseFile(FileRef);
  25.     Form1.StaticText2.Caption:= 'Done!';
  26.   END;
  27.  

I never see 'Reading...' and IntToStr(i), and I don't see the FilePath or progress bar action until the file operations are finished. The only thing that shows when I want it is 'Done!'

Is there some setting I'm missing?

Mac OS 13.2.1, fpc 3.2.2, Lazarus 4.0RC1

TRon

  • Hero Member
  • *****
  • Posts: 3928
Re: File operations blocking UI update
« Reply #1 on: January 13, 2025, 10:11:17 pm »
Is there some setting I'm missing?
Code: Pascal  [Select][+][-]
  1. Application.ProcessMessages;
  2.  
I do not have to remember anything anymore thanks to total-recall.

atlatl

  • New Member
  • *
  • Posts: 11
Re: File operations blocking UI update
« Reply #2 on: January 13, 2025, 10:33:41 pm »
Thank you!

TRon

  • Hero Member
  • *****
  • Posts: 3928
Re: File operations blocking UI update
« Reply #3 on: January 14, 2025, 01:15:30 am »
You're welcome  :)

Do keep in mind though that calling processmessages 100000 times might slow down actual performance (because the GUI is updated the same amount of times and that simply costs time).

The progress of a progressbar, for example, does not consist of 100000 pixels so that can be skipped a few times. You can also choose to update the statictext once per 50 or 100 reads or so.

The moment there is a significant slowdown in processing a loop you could use that kind tinkering to keep slowdown to a minimum. As an alternative you could run a lengthy loop or other operation inside its own thread and use some form of messaging or synchronization in order to update the GUI. However, that would be a bit more complicated to implement.
I do not have to remember anything anymore thanks to total-recall.

atlatl

  • New Member
  • *
  • Posts: 11
Re: File operations blocking UI update
« Reply #4 on: January 15, 2025, 01:12:50 am »
You're welcome  :)

Do keep in mind though that calling processmessages 100000 times might slow down actual performance (because the GUI is updated the same amount of times and that simply costs time).

The progress of a progressbar, for example, does not consist of 100000 pixels so that can be skipped a few times. You can also choose to update the statictext once per 50 or 100 reads or so.

The moment there is a significant slowdown in processing a loop you could use that kind tinkering to keep slowdown to a minimum. As an alternative you could run a lengthy loop or other operation inside its own thread and use some form of messaging or synchronization in order to update the GUI. However, that would be a bit more complicated to implement.

Your solution worked like a charm in the simple procedure I posted, but when I included processmessages in my actual code, it only sort of works.

I'm reading a binary file that's a square array of records that can be between 256 x 256 to 16k x 16k. There is an outer loop (y) that iterates throgh the rows of the array, and an inner loop (x) that iterates though each row. I increment the progress bar in the outer loop. In the inner loop I read a record, and place each field of that record in it's respective array.

What happens now, with the addition of processmessages, is the outer loop reaches it's halfway mark, the progress bar shoots from Min to Max, and then the outer loop runs the rest of it's course.

I take your point about not calling processmessages too often. I've been testing my app on smaller files, so updating the UI doesn't take much of toll, but I can see it becoming more of an issue with larger files.

I did take (an admittedly brief) look at threads, and yeah...no. 😊

Thanks again for the help.

cdbc

  • Hero Member
  • *****
  • Posts: 1808
    • http://www.cdbc.dk
Re: File operations blocking UI update
« Reply #5 on: January 15, 2025, 12:22:16 pm »
Hi
Make a callback procedure that takes as params (aPos,aMax: ptrint) and then call that in your inner loop with (looppos,loopmax);
Where init loopmax:= (outermax * innermax);
Where looppos starts as 0 and get incremented in inner loop before callback.
Just a thought  ;D
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

TRon

  • Hero Member
  • *****
  • Posts: 3928
Re: File operations blocking UI update
« Reply #6 on: January 15, 2025, 11:39:19 pm »
Your solution worked like a charm in the simple procedure I posted, but when I included processmessages in my actual code, it only sort of works.
As a quick 'n' dirty hack (dunno if it works for your use case but it is fairly easy and nearly non invasive to try out) you could try by using a timer. Set that timer to a sane interval (so not every millisecond but let's say a second as for an example). Enable the timer just before entering the loop and make sure to disable it after the loop is done. Inside the loop update a counter (or multiple counters if you want grained progress precision) and make it so that these counters are accessible outside the loop so that the timer event code is able to access them. Then inside the timer event calculate the (actual) progress if necessary and set the progressbar progress value and call processmessages. If this does not update the GUI as expected then you can try add a small sleep inside your loop at any given iteration (but as less as possible ofc). Tinker with the timerinterval and the number of times to call sleep (in case required) to get a more detailed control over the progressbar update.

A callback such as described by cdbc is also an option.

In case you know upfront how many bytes are going to be processed then you can set the progressbar to display the percentage from 0 to 100 and as such only requires a hundred updates through the whole file operation process (only update the progress bar if percentage is higher then previous percentage). That usually goes a bit wrong visually when there are only a few bytes (less than 100 bytes) to copy (the progress bar will progress in a choppy manner in that case)
« Last Edit: January 15, 2025, 11:51:41 pm by TRon »
I do not have to remember anything anymore thanks to total-recall.

atlatl

  • New Member
  • *
  • Posts: 11
Re: File operations blocking UI update
« Reply #7 on: January 16, 2025, 05:44:51 am »
So I have a working solution, but why it works is beyond me.
Instead of writing a callback, as cdbc suggested, I tried to put the processmessages call inside the inner loop. This resulted in the same result: at the halfway point of the outer loop the progress bar instantly goes from min to max. Then I thought, following cdbc's lead, I should somehow factor in inner loop counter in the increment of the progress bar.
From this:
Code: Pascal  [Select][+][-]
  1. Form2.ProgressBar1.Position:= trunc(j/jMax*100);
To this:
Code: Pascal  [Select][+][-]
  1. Form2.ProgressBar1.Position:= trunc(j*i/jMax*100);
This admittedly worked, but it slowed things down. I'll just divide it by some reasonable number,I thought, resulting in this:
Code: Pascal  [Select][+][-]
  1. Form2.ProgressBar1.Position:= trunc(j*i/i/jMax*100);

It worked! Ok, time for bed.
Woke up this morning, looked at that line of code, and thought: "What were you thinking? the i's just cancel out", and promptly erased them both... (so now I'm back at the original version that didn't work.) Recompiled it, and it worked!?
I must have made a mistake of some sort, I thought. But no. I rewrote the whole procedure with the original progress bar line. Compiled it... no go. Added the "trunc(j*i/i/jMax*100)" part, recompiled it. Works, but slow. Edited out the inner loop counters. Recompiled it, and it works just fine. Weird.

It works, I'm happy. Here's one for the X-Files.😎

Thanks to TRon and cdbc, for your help.

jamie

  • Hero Member
  • *****
  • Posts: 6798
Re: File operations blocking UI update
« Reply #8 on: January 16, 2025, 01:57:08 pm »
I think I would've used the file position as an indicator.
In a thread
The only true wisdom is knowing you know nothing

 

TinyPortal © 2005-2018