【问题标题】:reseting signaled waitable timer object handle重置信号等待计时器对象句柄
【发布时间】:2019-06-05 03:47:10
【问题描述】:

当我使用绝对时间和 waitformultipleobjects () 时,我遇到了一个可等待计时器对象的小问题。

一旦收到定时器信号,我需要重置对象,我能找到的唯一方法是再次使用 setwaitabletimer 并在未来不切实际的到期时间。这将使定时器在操作系统唤醒定时器中毫无理由地保持活动状态。 我需要保留句柄,因为在某些情况下我需要重新激活计时器,并且我使用 waitformultipleobjects 数组中的计时器对象,因此关闭 FWaitTimer 句柄并不好。

我的理解正确还是有更好的方法来做到这一点?

我的完整代码如下所示,请忽略其他 waitfor 对象,它只是 FWaitTimer 对象。

unit frmMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.WinXPickers, cxGraphics, cxControls, cxLookAndFeels, cxLookAndFeelPainters, dxSkinsCore,
  dxSkinTheAsphaltWorld, dxSkinsDefaultPainters, cxCalendar, Vcl.ExtCtrls, cxContainer, cxEdit, dxCore, cxDateUtils, cxDropDownEdit, cxTextEdit, cxMaskEdit,
  cxSpinEdit, cxTimeEdit, Vcl.Menus, Vcl.StdCtrls, cxButtons, dateutils, syncObjs, UTimer;

const
  WM_WATINGDONE = WM_USER + 100;

type

  TMyWaitThread = class(TThread)
  private
    FWaitTimer : THandle;
    FTerminateEvent : TEvent;
    FPeriodicTimer : TADTimer;
    FWaitTime: TDateTime;
    procedure SetWaitTime(const Value: TDateTime);
    property WaitTime : TDateTime read FWaitTime write SetWaitTime;
  public
    constructor Create; overload;
    constructor Create(CreateSuspended: Boolean); overload;
    destructor Destroy; override;
    procedure execute; override;
    procedure Terminate;
  end;



  TForm1 = class(TForm)
    cxClock1: TcxClock;
    Timer1: TTimer;
    cxDateEdit1: TcxDateEdit;
    btnWait: TcxButton;
    procedure Timer1Timer(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnWaitClick(Sender: TObject);
  private
    FMyWaitThread : TMyWaitThread;
    Fwaiting: Boolean;
    procedure WM_WAITINGDONE(var msg : TMessage); message WM_WATINGDONE;
    procedure Setwaiting(const Value: Boolean);
    { Private declarations }
    property waiting : Boolean read Fwaiting write Setwaiting;
    procedure WatingDone;


  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnWaitClick(Sender: TObject);
var
  dt : TDateTime;
begin
  dt := cxDateEdit1.EditValue;
  FMyWaitThread.SetWaitTime(dt);
  waiting := True;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  cxClock1.Time := now;
  Timer1.Enabled := True;
  waiting := False;

  FMyWaitThread := TMyWaitThread.Create;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FMyWaitThread.Terminate;
  FreeAndNil(FMyWaitThread);
end;

procedure TForm1.Setwaiting(const Value: Boolean);
begin
  Fwaiting := Value;
  case Fwaiting of
    True : begin
      btnWait.Enabled := False;
    end;

    false : begin
      btnWait.Enabled := True;
      cxDateEdit1.EditValue := IncMinute(Now, 1);
    end;
  end;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  cxClock1.Time := now;
end;

procedure TForm1.WatingDone;
begin
  waiting := False;
end;

procedure TForm1.WM_WAITINGDONE(var msg: TMessage);
begin
  waiting := False;
end;

{ TMyWaitThread }

function GetGUID : string;
var
  uid : TGUID;
  r : Integer;
begin
  Result := '';
  r := CreateGuid(Uid);
  if r = S_OK then
  begin
    Result := StringReplace(GuidToString(Uid),'{', '', [rfReplaceAll]);
    Result := StringReplace(result, '}', '', [rfReplaceAll]);
  end;
end;

constructor TMyWaitThread.Create(CreateSuspended: Boolean);
begin
  inherited Create(CreateSuspended);
  FWaitTimer := CreateWaitableTimer(nil, True, PWideChar(GetGUID)); // change the 'MatrixTimer' to something unique...
  FTerminateEvent := TEvent.Create;
  FPeriodicTimer := TADTimer.Create;
  FPeriodicTimer.Interval := 10000;
end;

destructor TMyWaitThread.Destroy;
begin
  FPeriodicTimer.StopTimer;
  FreeAndNil(FPeriodicTimer);
  FreeAndNil(FTerminateEvent);
  CloseHandle(FWaitTimer);
  inherited;
end;

procedure TMyWaitThread.execute;
var
  EventArr : array of THandle;
begin
  SetLength(EventArr, 3);

  EventArr[0] := FTerminateEvent.Handle;
  EventArr[1] := FWaitTimer;
  EventArr[2] := FPeriodicTimer.Handle;

  while not Terminated do
  begin
    try
      case WaitForMultipleObjects(Length(EventArr), @EventArr[0], False, INFINITE) of

        WAIT_OBJECT_0 : begin // terminate
          OutputDebugString('Terminating.....');
          Exit;
        end;

        WAIT_OBJECT_0+1 : begin // wait timer
          OutputDebugString('Wait timer was triggered');

          WaitTime := IncMinute(now, 1);

          // How do I reset the FWaitTimer - I would like to keep the FWaitTimer handle so closing it is no good.

          PostMessage(Form1.Handle, WM_WATINGDONE, 0, 0);
        end;

        WAIT_OBJECT_0+2 : begin // periodic timer
          OutputDebugString('Periodic timer was triggered');
        end;

      end;

    except on E: Exception do
      // keep any exceptions inside the loop
    end;
  end;
end;

procedure TMyWaitThread.SetWaitTime(const Value: TDateTime);
var
  WakeUpTime: LARGE_INTEGER;
  SysTime : _SystemTime;
  FTime : _FileTime;
begin
  FWaitTime := Value;
  DateTimeToSystemTime(FWaitTime, SysTime);
  SystemTimeToFileTime(SysTime, FTime);
  LocalFileTimeToFileTime(FTime, FTime);
  WakeUpTime.LowPart := FTime.dwLowDateTime;
  WakeUpTime.HighPart := FTime.dwHighDateTime;
  SetWaitableTimer(FWaitTimer, WakeUpTime.quadpart, 0, nil, nil, True);
end;

procedure TMyWaitThread.Terminate;
begin
  FTerminateEvent.SetEvent;
  inherited;
end;

constructor TMyWaitThread.Create;
begin
  Create(False);
end;

end.

【问题讨论】:

  • Resetting Waitable Timers的可能重复
  • 您需要在数组中动态更改对象,等待。在发出计时器信号后 - 从传递给 WaitForMultipleObjectsMsgWaitForMultipleObjectsEx 的数组中删除它。当它再次需要时 - 再次将其包含到数组中
  • 在您的情况下,最简单的方法是将 FWaitTimer 放在数组中的 2(最后)位置 - EventArr[2] := FWaitTimer 并将 3 或 2 作为第一个参数传递给您的代码中的 WaitForMultipleObjects。非常简单和清洁解决方案

标签: delphi winapi


【解决方案1】:

引用documentation(强调我的):

线程调用 SetWaitableTimer 函数来激活定时器。 请注意对 SetWaitableTimer 使用以下参数: lpDueTime 参数指定定时器的时间 设置为信号状态。 当手动复位定时器设置为 信号状态,它保持在这个状态直到 SetWaitableTimer 建立新的到期时间。 当同步计时器设置为 信号状态,它保持在这个状态,直到一个线程完成一个 等待定时器对象的操作。

所以唯一的办法就是再次调用SetWaitableTimer来重置定时器的信号状态。

有两种方法可以解决您的问题,使用当前的 hack 但在 SetWaitableTimer() 之后添加 CancelWaitableTimer() 以取消计时器(它的状态将保持未发出信号)

更好的解决办法是在不使用的时候不要等待定时器, 按照@RbMm 的建议实施:

将 FWaitTimer 放在数组中的第 2(最后)位置 - EventArr[2] := FWaitTimer 并将 3 或 2 作为第一个参数传递给 WaitForMultipleObjects

【讨论】:

  • 好的,谢谢你们,我理解这里的两个选项,并且可能会选择“当前黑客”,因为你表达它:) 在未来的项目中,我会看看我是否可以重新设计“更好”解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-17
  • 1970-01-01
相关资源
最近更新 更多