views:

231

answers:

5

my single threaded delphi 2009 app (not quite yet complete) has started to have a problem with Application.ProcessMessages hanging. my app has a TTimer object that fires every 100 ms to poll an external device. i use Application.ProcessMessages to update the screen when something changes so the app is still responsive.

one of these was in a grid OnMouseDown event. in there, it had an Application.ProcessMessages that essentially hung. removing that was no problem except that i soon discovered another Application.ProcessMessages that was also blocking.

i think what may be happening to me is that the TTimer is--in the app mode i'm currently debugging--probably taking too long to complete. i have prevented the TTimer.OnTimer event hander from re-entering the same code (see below):

procedure TfrmMeas.tmrCheckTimer(Sender: TObject);
begin
  if m_CheckTimerBusy then
    exit;

  m_CheckTimerBusy:=true;
  try
    PollForAndShowMeasurements;
  finally
    m_CheckTimerBusy:=false;
  end;
end;

what places would it be a bad practice to call Application.ProcessMessages? OnPaint routines springs to mind as something that wouldn't make sense.

any general recommendations?

i am surprised to see this kind of problem arise at this point in the development!

+2  A: 

I recommend this article: "How to debug application’s hang?" ;)

Alexander
+1  A: 

Use madExcept and you will see where is the deadlock.

inzKulozik
that's an idea i hadn't thought MadExcept would catch because the app doesn't seem hung because--in so many ways--it looks like it's running ok.
X-Ray
With madExcept you can also see callstacks of all threads.
inzKulozik
+1  A: 

My recommendation regarding TApplication.ProcessMessages is to never ever use it - there simply isn't a good point to place it.

Imagine what calling it does: your application runs a message loop - where the windows messages (produced by OS, other apps, your app etc) are sequentially processed - and there, in the middle of one of the message-processings, you just re-run the whole message loop again, without having control over what messages will be processed, how much of them there will be, if any of the messages will enter its' own message loop... and if they have any reentrancy problems or not. That's what I call inviting trouble.

There are sometimes good reasons to process some windows messages (particularry to not hang other threads), or to process all messagess directed to a particular window, but this may be accomplished in more subtle ways, with more control.

If you have to do any processing in the main GUI thread, and you just want to update the interface, you may use the TWinControl.Repaint method to redraw the GUI elements.
If you want to keep the app responsive to user input, you basically have to use backgroud/worker threads.

Notice: in Delphi, while doing any lenghty processing in the main thread, especcially if waiting is involved, you are supposed to call CheckSynchronize periodically, to allow any other threads to synchonize with the main thread - they may (and probably will) hang otherwise.
VCL calls this only when the app goes idle and when it processes the WM_NULL message (that is supossed to do nothing, which may cause some interesting side-effects too).

Viktor Svub
X-Ray
+1  A: 

thank you all for your comments/suggestions.

here i made a test app that has a timer routine that takes longer than the interval it is set for. when i push button1, Application.ProcessMessages hangs. my solution for now is to disable the timer during the timer routine.

later we plan to put the "device communications" in a thread.

thank you! mp

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    Button1: TButton;
    Memo1: TMemo;
    procedure Timer1Timer(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  memo1.Lines.Add('text 1');

  // this call blocks
  Application.ProcessMessages;

  memo1.Lines.Add('text 2');
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  iTime:cardinal;
begin
  // fix by adding this:  timer1.Enabled:=false;

  iTime:=GetTickCount;
  while GetTickCount-iTime<200 do
    ;

  // fix by adding this:  timer1.Enabled:=true;
end;

end.

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 286
  ClientWidth = 426
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 8
    Top = 56
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click
  end
  object Memo1: TMemo
    Left = 112
    Top = 8
    Width = 297
    Height = 233
    Lines.Strings = (
      'Memo1')
    TabOrder = 1
  end
  object Timer1: TTimer
    Interval = 100
    OnTimer = Timer1Timer
    Left = 200
    Top = 144
  end
X-Ray
A: 

It seems terrible style to rely heavily on timer-based logic, and you understand that as you say you are planning to use threads in the future.

Here's the progression in your code:

  1. You have a timer window message. it does a small job.
  2. Six months later, your timer is running a lot of code.
  3. you think you could make things better by putting in Application.ProcessMessages.

Let's zoom in a little, on a timer with 100 msec interval, for a worst case:

09:00:00.000 - timer event fires
09:00:00.100 - we are half way through our Timer Event code. We hit an Application.ProcessMessages.
09:00:00.101 - timer event fires again, but the first time into it, has not yet completed.
09:00:00.200 - we are half way through our timer event code, the second time,
and hit APplication.ProcessMessages again.

Can you see the... Can you see the.... Can you see the..... problem here?

W

Warren P
yes; the previous versions of the app used a slower (1000 ms) timer fine. partway through this development cycle they told me the polling rate. the other parts of this project are already quite ambitious and also behind schedule...no time to "reach" farther and put it in a thread yet--especially since my experience with threads is *almost* 0. thank you for your comment!
X-Ray