Strange behaviour of function Sleep() used in repeat until in Delphi
Asked Answered
E

4

5

I have function which is reaction on button click. When I click on the button it should start repeat and write values form an array and show them in labels on main form. Problem is with function sleep - there is some bug or something, cause when I click on the button it waits quite a long time and then it finaly start the action but very quickly. Let's look at my code. Thx for advices.

procedure TForm1.ButtonMereniClick(Sender: TObject);
var
  iterator: Integer;
begin      
  iterator := 1;
  repeat       
    //write some values stored int arrays to labels on form
    LabelTeplota.Caption:='Teplota: '+FloatToStr(poleTeplota[iterator]);
    LabelVlhkost.Caption:='Vlhkost: '+FloatToStr(poleVlhkost[iterator]);
    LabelTlak.Caption:='Atmosférický tlak: '+FloatToStr(poleTlak[iterator]);
    LabelRychlost.Caption:='Rychlost větru: '+FloatToStr(poleRychlost[iterator]);
    LabelRychlost.Caption:='Rychlost větru: '+FloatToStr(poleRychlost[iterator]);
    LabelIterator.Caption:='iterator: '+IntToStr(iterator);
    Sleep(500);//should be 5000 
    Inc(iterator);
  until iterator = 20;
end;
Elegit answered 11/3, 2012 at 14:59 Comment(10)
I live by this maxim: "If you feel the need to use Sleep(), you are doing it wrong."Sidky
@nick Indeed. My equivalent is "There are no problems for which Sleep is the solution."Chadwick
@NickHodges etc. How then, do you satisfy the requirement for a pause when several levels deep on a thread stack? Let me guess - 'spend ages re-implementing the procedurally-written spec as a state-machine, just so that a timer can be used instead of sleep() calls'. There is no doubt that A.P + sleep() loops in GUI handlers are a particularly lame misuse, but the 'sleep can be misused, so don't use it at all' argument is err.. 'difficult to support'.Copley
@Martin Sleep is not interruptible.Chadwick
@DavidHeffernan - if by 'not interruptible', you mean that the state of the thread cannot be changed by another user thread before the interval is up then, yes. If a thread needs to be signaled in such a manner then, yes, don't use sleep(). An event or sema wait with a timeout is fine for such a requirement. If there is no need for such signaling, then use sleep().Copley
If you ask me, I'd say a Thread is the way to go.Foe
@Jerry Then you have to Synchronize the UI updates to the UI thread. Why would a thread be better than a timer?Chadwick
@DavidHeffernan Flexibility, depending on what needs to be done. At the same time, calculations could be done within the thread (not saying user needs such ability). If I knew what language was being used above, I could tell for sure, knowing what info it's actually displaying. For example, FloatToStr (I would use FormatFloat) could be used inside the thread, temporarily storing the converted (or even calculated or translated) values, then the GUI refresh already has these converted values ready to read, instead of converting them on the spot.Foe
And if, for example, the user happened to need to add one calculated value (such as bytes-per-second) to display, then the thread can take care of such possibly lengthy operations and execute an event every time some info has changed, which in turn updates the UI.Foe
To sum everything up, a simple line of code like Sleep(X); while X = some huge number like 60,000, then the application will be frozen for 1 minute (60,000 milliseconds). Uninterruptable roughly translates to "Not able to tell the application to STOP the Sleep command"Foe
I
19

Don't use Sleep function in GUI thread because you are blocking normal message processing and your application behaves strangely.

It is not clear why you use Sleep in your code. Probably you should update labels from OnTimer event handler of TTimer component instead of using a loop.

Ilarrold answered 11/3, 2012 at 20:46 Comment(1)
+1 This is the correct answer. Blocking the GUI thread is always a bad move. Use a timer.Chadwick
S
1

I used two timers to update "slowly" some values (so that the user can see the progress) without slowing down the application or, in the worst case, the system.

//  "weights" for old and new value; I was working on integers
var
  Wa: integer =  1;
  Wb: integer =  9;
  Wt: integer = 10;

//  interval of 1000 ms, of course
procedure frmMain.T1000Timer(Sender: TObject);
begin
  Wa:= 1;
  nValue:= calculate_new_value;
end;

//  100 ms interval
procedure frmMain.T100Timer(Sender: TObject);
begin
  Wb:= Wt -Wa;
  //  displayed value, ie gauge.progress, or something.Value, etc.
  sam.Value:= Round( (Wb *sam.Value +Wa *nValue) /Wt );
  if Wa < Wt then Inc(Wa);
end;
Sibella answered 21/12, 2013 at 16:9 Comment(0)
P
0

If you must use a delay or "sleep" type function, you can use a procedure with ProcessMessages. There are some plusses and minuses to using it, but I have successfully on many occasions with no ill effects. I know there are others here who can better comment on ProcessMessages.

Procedure Delay(MSecs: Cardinal);
var
 FirstTick, CurrentTick : Cardinal;
 Done : Boolean;
begin
 Done := FALSE;
 FirstTick := GetTickCount;
 While Not Done do
  begin
   Application.ProcessMessages;
   CurrentTick := GetTickCount;
   If Int64(CurrentTick) - Int64(FirstTick) < 0 Then
    begin
     If CurrentTick >= (Int64(FirstTick) - High(Cardinal) + MSecs) Then
      Done := TRUE;
       End
        Else
         If CurrentTick - FirstTick >= MSecs Then
          Done := TRUE;
  end;
end;

// Below for a service

procedure YourSvrSvc.ProcessMessages;
var
  Msg: TMsg;
begin
  if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
  begin
    TranslateMessage(Msg);
    DispatchMessage(Msg);
  end;
end;

Procedure YOURSvrSvc.Delay(MSecs: Cardinal);
var
 FirstTick, CurrentTick : Cardinal;
 Done : Boolean;
begin
 Done := FALSE;
 FirstTick := GetTickCount;
 While Not Done do
  begin
   YOURSvrSvc.ProcessMessages;
   CurrentTick := GetTickCount;
   If Int64(CurrentTick) - Int64(FirstTick) < 0 Then
    begin
     If CurrentTick >= (Int64(FirstTick) - High(Cardinal) + MSecs) Then
      Done := TRUE;
       End
        Else
         If CurrentTick - FirstTick >= MSecs Then
          Done := TRUE;
  end;
end;
Preengage answered 11/3, 2012 at 23:15 Comment(5)
ProcessMessages opens you up to re-entrant calls to your event handler.Chadwick
@David Yes sir! I did mention that there are pluses and minuses, but I thought that might be going beyond the scope of the OP. But did give him an option.Preengage
I only see downsides to using ProcessMessages.Chadwick
Not to mention the busy loop.Chadwick
Here, I agree 100% with @DavidHeffernan. A.P is an admission of design defeat. Unnecesary polling, latency and reentrancy risk :((Copley
F
0

It's difficult for me to say not to use Sleep since I myself use it all the time, but Application.ProcessMessages is in fact a dangerous solution, especially when used in a loop. I'm not sure what information you're displaying (since I don't recognize the language) but it looks like you're doing some conversions from Float to String. Although these conversions are performed in a split second, add them all together and you could come up with a lengthy operation. And suppose you decide to add another value to be updated, which requires some calculation (such as bytes per second in a file transfer). This conversion will add a bit more time onto this operation, and before you know it, you could wind up with a UI update which takes half a second (which doesn't seem long, but when it comes to processor usage, this is quite a load).

Therefore, I would suggest using a Thread to perform all of these conversions, calculations, etc. and trigger events as needed whenever that information has changed. Now a thread will definitely be a little more complex than the other suggested solutions here, no doubt. But using a thread can mean a great deal of benefits too. All your heavy work can be done in the background while your application is still responding perfectly. Keep in mind that using a thread can be very tricky, specifically when it comes to UI updates.

There's a few ways to make a thread, but I'll try to make this simple...

type
  TMyThread = class;

  TMyThreadEvent = procedure(Sender: TObject; const Val1, Val2: String) of object;

  TMyThread = class(TThread)
  private
    FValue1: Integer;
    FValue2: Integer;
    FString1: String;
    FString2: String;
    FOnChange: TMyThreadEvent;
    procedure SYNC_OnChange;
  protected
    procedure Execute; override;
  public
    constructor Create;
    property Value1: Integer read FValue1 write FValue1;
    property Value2: Integer read FValue2 write FValue1;
    property String1: String read FString1;
    property String2: String read FString2;
    property OnChange: TMyThreadEvent read FOnChange write FOnChange;
  end;

  ...

  constructor TMyThread.Create;
  begin
    inherited Create(False);
    FValue1 := '0';
    FValue2 := '0';
  end;

  procedure TMyThread.Execute;
  var
    S1, S2: String;
    DoChange: Bool;
  begin
    DoChange:= False;
    FValue2:= DoSomeBigCalculation(FValue1); //Some random big calculation
    S1:= FormatFloat('#,##0.#', FValue1);
    S2:= FormatFloat('#,##0.#', FValue2);
    if (S1 <> FString1) then begin
      FString1:= S1;
      DoChange:= True;
    end;
    if (S2 <> FString2) then begin
      FString2:= S2;
      DoChange:= True;
    end;
    if DoChange then
      Synchronize(SYNC_OnChange);
  end;

  procedure TMyThread.SYNC_OnChange;
  begin
    if assigned(FOnChange) then
      FOnChange(Self, FString1, FString2);
  end;

Now to use this, you would set the Integer properties as needed. Make sure you set the OnChange event to a procedure with the parameters of the above TMyThreadEvent type. Whenever any value differs from its original (or old) value, it will trigger this event. I would also highly recommend that whatever processing code you might have which produces these values in the first place be put inside of a thread. The possibilities of multi-threading is vast and prove a great advantage in applications which have a lot going on in them.

Please note that my above code is just a sample typed directly into this website, and is not tested. It's just to give you an idea of how to implement a thread to do your updating.

Foe answered 12/3, 2012 at 22:15 Comment(1)
PS - You could take this a step further and create separate events, one for each property. Then there would be events like OnTeplota, OnVlhkost, etc. which return the new value.Foe

© 2022 - 2024 — McMap. All rights reserved.