循环播放而不会导致应用程序冻结

时间:2018-07-16 11:33:54

标签: delphi while-loop freeze

我想编写一个循环来检查变量的值是否已更改。没有事件可以告诉我值已更改。 该应用程序不支持多线程。 如何在不导致应用程序冻结的情况下实现这一目标?

目标是:

Application starts
...
loop
  Check variable value
  If changed then
    exit
  if timedOut then 
    exit

虽然循环导致应用程序冻结。

谢谢。

*编辑* 这就是我想要的(这段代码是由Remy Lebeau编写的):

const
  APPWM_COM_EVENT_DONE = WM_APP + 1;
  APPWM_COM_EVENT_TIMEOUT = WM_APP + 2;

type
  MyClass = class
  private
  MsgWnd: HWND;
  procedure COMEventHandler(parameters);
  procedure WndProc(var Message: TMessage);
public
  constructor Create;
  destructor Destroy; override;
  procedure DoIt;
end;

constructor MyClass.Create;
begin
  inherited;
  MsgWnd := AllocateHWnd(WndProc);
end

destructor MyClass.Destroy;
begin
   KillTimer(MsgWnd, 1);
   DeallocateHWnd(MsgWnd);
   inherited;
end;

procedure MyClass.COMEventHandler(parameters);
begin
  KillTimer(MsgWnd, 1);
  PostMessage(MsgWnd, APPWM_COM_EVENT_DONE, 0, 0);
end;

procedure MyTimer(hWnd: HWND; uMsg: UINT; idEvent: UINT_PTR; dwTime:   DWORD); stdcall;
begin
  KillTimer(hWnd, idEvent);
  PostMessage(hWnd, APPWM_COM_EVENT_TIMEOUT, 0, 0);
end;

procedure MyClass.WndProc(var Message: TMessage);
begin
  case Message.Msg of
   APPWM_COM_EVENT_DONE:
   begin
      // Event fired, all good
    end;

    APPWM_COM_EVENT_TIMEOUT:
    begin
      // Event timed out
    end;

  else
  begin
    Message.Result := DefWindowProc(MsgWnd, Message.Msg, Message.WParam, Message.LParam);
   end;
 end;
end;

procedure MyClass.DoIt;
begin
  SetTimer(MsgWnd, 1, 1000 * 1000, @MyTimer);
  // invoke COM function that will eventually trigger the COM event...
 end;

如何调用DoIt并等待事件触发或超时而不会导致应用程序冻结? 尝试使用while do循环,但是这阻止了WndProc的运行。 谢谢

1 个答案:

答案 0 :(得分:-1)

答案取决于您的应用程序需求。有2个简单的解决方案,每个解决方案都有优点和缺点: 1.将定时器置于应用程序并按超时检查值。尊严-这是GUI应用程序最简单的方法(Windows消息循环已存在),另一方面则是缺点-检测值的增量时间已更改。 2.处理Application.OnIdle事件。这种方法的缺点-如果没有人单击GUI元素,则将运行检查过程。

解决方案的专业方法-用复杂的对象包装变量,例如:

Trigger = class
private
  FOnChanged: TNotifyEvent;
public
  procedure Emit;
  property OnChanged: TNotifyEvent read FOnChanged write FOnChanged;
end;


procedure Trigger.Emit;
  if Assined(FOnChanged) then 
    FOnChanged(Self)
end;

您的应用程序没有线程的原因,我们可以在没有互斥锁/关键部分的情况下实现Trigger,另一方面,只要事件产生器提高Emit,您就可以处理更改

如果您不想使用多线程,那么一种好的方法是根据协程在多个状态机上分割您的逻辑。

基于AIO框架https://github.com/Purik/AIO

的示例

AIO框架创建自己的事件循环,在没有线程的情况下并行调度多个状态机:

program TriggerExample;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  SyncObjs,
  Gevent,
  Greenlets;

const
  WAIT_TMEOUT_MSEC = 1000;

var
  ChangedEvent: TGevent;
  Value: Boolean = False;


// Part of application that raise change events randomly
procedure EventsProducer;
begin
  while True do
  begin
    Greenlets.GreenSleep(100+Random(10000));
    Value := True;
    ChangedEvent.SetEvent;
  end;
end;


begin

  ChangedEvent := TGevent.Create(False, False);
  // run fake event producer inside other state machine
  TSymmetric.Spawn(EventsProducer);

  // Loop
  while True do
  begin
    if ChangedEvent.WaitFor(WAIT_TMEOUT_MSEC) = wrSignaled then
    begin
      WriteLn('Value was changed');
      Value := False
    end
    else
    begin
      WriteLn('Exit by timeout');
    end;

  end;

end.