Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: WinAPI и системное программирование > почему не срабатывает таймер?


Автор: Dmitry_177 29.10.2007, 13:55
Есть ini-файл, из него считывается время срабатывания таймеров(их несколько) но почему то не срабатывает таймер.. Приведу весь кусок кода:

Код

program TimeServ;

uses
 Windows,
 Messages,
 Classes,
 IniFiles,
 SysUtils;

var
 AMessage: TMsg;
 hTimers: array of THandle;

procedure TimerAPCProc(lpArgToCompletionRoutine: PChar; dwTimerLowValue: DWORD; dwTimerHighValue: DWORD); stdcall;
begin
 MessageBox(0, '1', '1', 0);  // не срабатывает почему то..
end;

function InstallTimers: BOOL;
var
 Sections: TStrings;
 iTimer: integer;
 sysTime: SYSTEMTIME;
 fTime: FILETIME;
begin
   try
     Sections := TStringList.Create;

     with TIniFile.Create('TimeManager.ini') do
       try
         ReadSections(Sections);
         if Sections.Count > 0 then
           begin
             SetLength(hTimers, Sections.Count);
             for iTimer := 0 to Sections.Count - 1 do
               begin
                 hTimers[iTimer] := CreateWaitableTimer(nil, false, nil);
                 DateTimeToSystemTime(StrToDateTime(Sections[iTimer]), sysTime);  // запись происходит функцией DateTimeToStr, в переменной sysTime смотрел дата с временем правильно считывается..
                 SystemTimeToFileTime(sysTime, fTime);
                 LocalFileTimeToFileTime(fTime, fTime);
                 SetWaitableTimer(hTimers[iTimer], TLargeInteger(fTime), 0, @TimerAPCProc, nil, false);
               end;
           end;
       finally
         Free;
       end;

   finally
     Sections.Free;
   end;
end;

function UninstallTimers: BOOL;
var
 iTimer: integer;
begin
 for iTimer := 0 to Length(hTimers) - 1 do
   begin
     CancelWaitableTimer(hTimers[iTimer]);
     CloseHandle(hTimers[iTimer]);
   end;
 SetLength(hTimers, 0);
end;

begin
 InstallTimers;

 while GetMessage(AMessage, 0, 0, 0) do
   DispatchMessage(AMessage);

 UninstallTimers;
end.


Подскажите пожалуйста..

Автор: Alix 29.10.2007, 14:45
начнем с того, что ты не создаешь окна, а сообщения могут слаться только ему, так что цикл GetMessage просто не имеет смысла

Автор: Dmitry_177 29.10.2007, 15:05
а как тогда быть?

Автор: maxim1000 29.10.2007, 15:11
для того, чтобы вызывалась асинхронная процедура, нужно, чтобы поток был в прерываемом состоянии
например, можно использовать SleepEx

Автор: Dmitry_177 29.10.2007, 18:09
А вот как сделать так чтобы все таймеры сработали? Что-то я не придумаю никак.. Может SleepEx в бесконечный цикл поставить? Ведь программа вылетает при первом же срабатывании..

Автор: Dmitry_177 29.10.2007, 23:03
я сделал в цикле SleepEx, вроде все работает =) только у меня еще один вопрос возник.. Мне нужно в процедуру TimerAPCProc передать значение Sections[iTimer], т.е. дату с временем в строке, делаю так:

Код

SetWaitableTimer(hTimers[iTimer], TLargeInteger(fTime), 0, @TimerAPCProc, PChar(Sections[iTimer]), false);


а в lpArgToCompletionRoutine обрезанная строка, т.е. например исходная такая: 29.10.2007 21:33:00 а в процедуре она такая: 29.10.2007 21:33

Автор: maxim1000 30.10.2007, 00:13
а зачем делать SleepEx в цикле?
туда же сразу можно написать нужный период или INFINITE

Автор: Dmitry_177 30.10.2007, 10:03
у меня какраз вот так:

Код

while true do
  SleepEx(INFINITE, True);


При вызове TimerAPCProc, SleepEx завершается..

Автор: MetalFan 30.10.2007, 10:41
хм, продублирую и здесь)
Цитата(Dmitry_177 @  29.10.2007,  23:03 Найти цитируемый пост)
а в lpArgToCompletionRoutine обрезанная строка, т.е. например исходная такая: 29.10.2007 21:33:00 а в процедуре она такая: 29.10.2007 21:33 

словишь в конце-концов AV, если так оставишь.
у тебя Sections прибивается в InstallTimers....

Автор: maxim1000 30.10.2007, 12:10
Цитата(Dmitry_177 @  30.10.2007,  10:03 Найти цитируемый пост)
При вызове TimerAPCProc, SleepEx завершается.. 

а... это я стормозил, документацию не прочитал...

Автор: Dmitry_177 30.10.2007, 14:30
Еще маленький вопросик, можно ли в TimerAPCProc определить какой таймер сработал? Ведь если таймер с периодом он так и будет дергать TimerAPCProc, так вот если уже больше он не понадобится, чтоб можно его было хлопнуть в этой же TimerAPCProc..

Автор: maxim1000 30.10.2007, 17:02
а не проще ли использовать WaitForMultipleObjects?
там и, какой объект сработал, узнать проще простого, и всякие асинхронные процедуры не нужно городить...

Автор: Dmitry_177 30.10.2007, 17:21
в ini файле время срабатывания может и не по порядку быть.. получится так что считается первым более позднее время срабатывания чем раннее?

Автор: Dmitry_177 30.10.2007, 19:00
Блин! Решил еще добавить событие на считывание новых значений из этого же файла.. SleepEx уже не спасает..


Код

function ThreadEvent(Param: Pointer): DWORD; stdcall;
begin
 while true do
   begin
     if WaitForSingleObject(hEvent, INFINITE) = WAIT_OBJECT_0 then
       begin
         UninstallTimers;
         InstallTimers;
       end;
   end;
 Result := 0;
end;

...
 InstallTimers;

 hEvent := CreateEvent(nil, false, false, EventName);
 CloseHandle(CreateThread(nil, 0, @ThreadEvent, nil, 0, idThread));

 while true do
   SleepEx(INFINITE, True);


Добавлено через 7 минут и 43 секунды
т.е. дело в том что событие не срабатывает.. сделал трассировку, в функции события ThreadEvent останавливается на WaitForSingleObject и все..

Автор: maxim1000 30.10.2007, 21:08
Цитата(Dmitry_177 @  30.10.2007,  19:00 Найти цитируемый пост)
в функции события ThreadEvent останавливается на WaitForSingleObject и все..

так а кто ж его дёрнет, чтобы WaitForSingleObject вышла?

Автор: MetalFan 30.10.2007, 21:17
кста, и почему bManualReset = false?

Автор: Dmitry_177 30.10.2007, 21:54
Цитата

так а кто ж его дёрнет, чтобы WaitForSingleObject вышла?

в другой программе..

Автор: Dmitry_177 30.10.2007, 22:56
Цитата

кста, и почему bManualReset = false?

событие с автоматическим сбросом =)

Автор: MetalFan 31.10.2007, 08:58
Dmitry_177, а зачем? ) если "ожидатель" только один, то ладно.

Автор: maxim1000 31.10.2007, 12:06
Цитата(Dmitry_177 @  30.10.2007,  21:54 Найти цитируемый пост)
в другой программе..

ну, похоже на то, что программа та программа не дёргает...

Добавлено через 2 минуты и 46 секунд
кстати, а та программа только дёргает или тоже ждёт этого события?
просто если оно автоматически сбрасываемое, то может получиться так, что две программы ждут, оно дёргается, одна программа дожидается, событияеавтоматически сбрасывается, и вторая программа не дожидается...

Автор: Dmitry_177 31.10.2007, 19:41
не.. та именно дергает.. вот код из другой программы которая дергает:

Код

  hEvent := CreateEvent(nil, false, false, EventName);
  SetEvent(hEvent);
  CloseHandle(hEvent);


EventName - строковая константа, и там и там одна и таже..

Автор: MetalFan 31.10.2007, 20:10
Dmitry_177, а где проверка на возможные ошибки?

Автор: Dmitry_177 31.10.2007, 20:29
MetalFan, чтобы сократить код я их убрал, я показал сам смысл я в той программе дергаю это событие..

Автор: maxim1000 31.10.2007, 22:32
хм... а не получается ли так:
1. первая программа создаёт событие
2. дёргает его
3. успевает удалить его до того, как его откроет вторая программа
4. вторая программа создаёт уже новое событие
и т.д.
?

Автор: Dmitry_177 31.10.2007, 22:44
Цитата

хм... а не получается ли так:
1. первая программа создаёт событие
2. дёргает его
3. успевает удалить его до того, как его откроет вторая программа
4. вторая программа создаёт уже новое событие
и т.д.
?

да нет.. не может быть такого в моем случае..

я вот думаю, может этот SleepEx срабатывает первей и не дает сработать WaitForSingleObject-у в другом потоке?

Автор: maxim1000 1.11.2007, 00:15
в общем, лучше было бы выложить тут два текста программ, которые ведут себя вышеописанным способом, если они, конечно, не очень большие (тогда их стоит минимизировать с сохранением непонятного поведения)

Автор: Dmitry_177 1.11.2007, 01:15
Ок, и так поехали:

первая программа, та которая устанавливает таймеры и реагирует на событие для считывания новых таймеров из файла(запущена постоянно):

Код

program TimeServ;

uses
  Windows,
  Messages,
  ShFolder,
  Classes,
  IniFiles,
  SysUtils;

const
  EventName = '{58F4D8A4-CAD7-46DA-A3D1-238B95659E68}';

type
  PDataTimer = ^TDataTimer;

  TDataTimer = packed record   // структура, адрес переменной которой передается в TimerAPCProc
    hTimer: THandle;
    StrDateTime: PChar;
  end;

var
  hEvent: THandle;
  idThread: DWORD;
  hTimers: array of THandle;
  StrFilePath: string;
  Sections: TStrings;

function GetStrFilePath: string;
var
  StrBuffer: array [0 .. MAX_PATH - 1] of Char;
  StrAppDataFolder: string;
begin
  ShGetFolderPath(0, CSIDL_APPDATA, 0, 0, StrBuffer);
  StrAppDataFolder := StrBuffer + '\TimeManager';
  if not DirectoryExists(StrAppDataFolder) then
    ForceDirectories(StrAppDataFolder);
  Result := StrAppDataFolder + '\TimeManager.ini';
end;

procedure TimerAPCProc(lpArgToCompletionRoutine: PDataTimer; dwTimerLowValue: DWORD; dwTimerHighValue: DWORD); stdcall;
begin
  with TIniFile.Create(StrFilePath) do
    try
      if MessageBox(0, PChar(ReadString(lpArgToCompletionRoutine.StrDateTime, 'Note', '') + #13#10#13#10 + 'Удалить это напоминание?'), 'Напоминание', MB_ICONQUESTION or MB_YESNO) = idYes then
        begin
          CancelWaitableTimer(lpArgToCompletionRoutine.hTimer);
          CloseHandle(lpArgToCompletionRoutine.hTimer);
          EraseSection(lpArgToCompletionRoutine.StrDateTime);
          Dispose(lpArgToCompletionRoutine);
        end;
    finally
      Free;
    end;
end;

procedure InstallTimers;
var
  iTimer: integer;
  sysTime: SYSTEMTIME;
  fTime: FILETIME;
  DataTimer: PDataTimer;
begin
  with TIniFile.Create(StrFilePath) do
    try
      ReadSections(Sections);
      if Sections.Count > 0 then
        begin
          SetLength(hTimers, Sections.Count);
          for iTimer := 0 to Sections.Count - 1 do
            begin
              hTimers[iTimer] := CreateWaitableTimer(nil, false, nil);
              DateTimeToSystemTime(StrToDateTime(Sections[iTimer]), sysTime);
              SystemTimeToFileTime(sysTime, fTime);
              LocalFileTimeToFileTime(fTime, fTime);
              New(DataTimer);
              DataTimer.hTimer := hTimers[iTimer];
              DataTimer.StrDateTime := PChar(Sections[iTimer]);
              SetWaitableTimer(hTimers[iTimer], TLargeInteger(fTime), Round(Frac(StrToDateTime(ReadString(Sections[iTimer], 'Period', '0'))) * 86400) * 1000, @TimerAPCProc, DataTimer, false);   // период записывается в формате DateTime, перевожу в миллисекунды
            end;
        end;
    finally
      Free;
    end;
end;

procedure UninstallTimers;
var
  iTimer: integer;
begin
  for iTimer := 0 to Length(hTimers) - 1 do
    begin
      CancelWaitableTimer(hTimers[iTimer]);
      CloseHandle(hTimers[iTimer]);
    end;
  SetLength(hTimers, 0);
  Sections.Clear;
end;

function ThreadEvent(Param: Pointer): DWORD; stdcall;
begin
  while true do
    begin
      if WaitForSingleObject(hEvent, INFINITE) = WAIT_OBJECT_0 then
        begin
          UninstallTimers;
          InstallTimers;
        end;
    end;
  Result := 0;
end;

begin
  StrFilePath := GetStrFilePath; 
  Sections := TStringList.Create;

  InstallTimers;

  hEvent := CreateEvent(nil, false, false, EventName);
  CloseHandle(CreateThread(nil, 0, @ThreadEvent, nil, 0, idThread));

  while true do
    SleepEx(INFINITE, True);

  UninstallTimers;
  Sections.Free;
end.


теперь вторая, та которая записывает таймеры и дергает событие(запускается нерегулярно):
в ней в принципе ничего сложного нету, она записывает в файл таймеры, которые вводятся с использованием форм и по завершению дергает событие.. Я думаю саму запись ненужно показывать, приведу FormClose, который собственно и дергает событие:

Код

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);   // Close
var
  hEvent: THandle;
begin
  hEvent := CreateEvent(nil, false, false, EventName);
  if hEvent = INVALID_HANDLE_VALUE then
    Exit;
  if GetLastError = ERROR_ALREADY_EXISTS then
    SetEvent(hEvent);
  CloseHandle(hEvent);
end;


Код трассировал, SetEvent срабатывает, т.е. само событие 100% дергается..

Автор: dumb 1.11.2007, 04:06
Цитата(Dmitry_177 @  30.10.2007,  19:00 Найти цитируемый пост)
т.е. дело в том что событие не срабатывает..
подозреваю, что событие то срабатывает, а вот таймеры после переустановки - уже нет. и причина тому очевидна - ты устанавливаешь заново таймеры в рабочем потоке, который вовсе не находится в alertable-состоянии во время ожидания события hEvent.

надо сделать примерно так:
Код

function ThreadEvent(Param: Pointer): DWORD; stdcall;
begin
  while true do
    begin
      if WaitForSingleObjectEx(hEvent, INFINITE, true) = WAIT_OBJECT_0 then
        begin
          UninstallTimers;
          InstallTimers;
        end;
    end;
  Result := 0;
end;

begin
  StrFilePath := GetStrFilePath;
  Sections := TStringList.Create;
//  InstallTimers;
  hEvent := CreateEvent(nil, false, false, EventName);
  SetEvent(hEvent); // initial install timers
  hThread := CreateThread(nil, 0, @ThreadEvent, nil, 0, idThread);
  WaitForSingleObject(hThread, INFINITE);
  CloseHandle(hThread);
  UninstallTimers;
  Sections.Free;
end.

Автор: MetalFan 1.11.2007, 08:49
давай код примера приложения в аттач... что так пальцем в небо тыкать...

Автор: Dmitry_177 1.11.2007, 23:52
Я сделал вот так:

Код

InstallTimers;

hEvent := CreateEvent(nil, false, false, EventName);
while true do
 begin
   if WaitForSingleObjectEx(hEvent, INFINITE, True) = WAIT_OBJECT_0 then
     begin
       UninstallTimers;
       InstallTimers;
     end;
 end;


и все заработало  smile 

кстати мы создаем в InstallTimers, структуры TDataTimer: New(DataTimer); удаляем их в TimerAPCProc: Dispose(lpArgToCompletionRoutine);.. А если время срабатывания будет удалено в той второй программе, то получается DataTimer не удалится?

Автор: Dmitry_177 5.11.2007, 14:02
на самом деле как тогда удалять DataTimer? вот задам я таймер в той программе, в этой создастся DataTimer и установится таймер.. А потом в той программе удалю этот таймер.. то что будет с DataTimer? Как его удалять?

Автор: dumb 5.11.2007, 15:56
Цитата(Dmitry_177 @  5.11.2007,  14:02 Найти цитируемый пост)
на самом деле как тогда удалять DataTimer?
тебе надо вместо массива хэндлов hTimers сделать массив(или там TList) DataTimer'ов. в таймерном callback'е "шерстить" этот массива на предмет переданного DataTimer, и удалять его - и самого и из массива.

Автор: Dmitry_177 8.11.2007, 01:57
Цитата

в таймерном callback'е "шерстить" этот массива на предмет переданного DataTimer, и удалять его - и самого и из массива.

сделал так.. и тут вот какая вещь получается.. при удалении в таймерном каллбеке например первого элемента такого массива, естественно я его сдвигаю на один элемент.. и получается так что в остальных таймерах ссылки на элементы массива сбиваются на один элемент.. т.е. второй сработавший каллбек от второго таймера ссылка которому была передана второго элемента уже считывается из того, который был на тот момент третьим(но в данный момент уже второй).. вообщем я не знаю, может немного и непонятно выразился, но как-то так оно и происходит.. Сбивается текст сообщения, выводится других таймеров и сами таймеры не те отключаются(те которые идут следующими по порядку в массиве)

Автор: dumb 8.11.2007, 02:55
Цитата(Dmitry_177 @  8.11.2007,  01:57 Найти цитируемый пост)
и получается так что в остальных таймерах ссылки на элементы массива сбиваются на один элемент.. 
э-э! я разве что-то говорил про передачу ссылки на элементы массива?! хотя согласен - немного неточно описал свою мысль. smile
имелось ввиду примерно следущее:
Код

var
  ...
  dta: TList;
//  hTimers: array of THandle;
...
procedure TimerAPCProc(lpArgToCompletionRoutine: PDataTimer; dwTimerLowValue: DWORD; dwTimerHighValue: DWORD); stdcall;
begin
  with TIniFile.Create(StrFilePath) do
    try
      if MessageBox(0, ...) = idYes then
        begin
          dta.Remove(lpArgToCompletionRoutine);
          CancelWaitableTimer(lpArgToCompletionRoutine.hTimer);
          CloseHandle(lpArgToCompletionRoutine.hTimer);
          EraseSection(lpArgToCompletionRoutine.StrDateTime);
          Dispose(lpArgToCompletionRoutine);
        end;
    finally
      Free;
    end;
end;
procedure InstallTimers;
var
  iTimer: integer;
  sysTime: SYSTEMTIME;
  fTime: FILETIME;
  DataTimer: PDataTimer;
begin
  with TIniFile.Create(StrFilePath) do
    try
      ReadSections(Sections);
      if Sections.Count > 0 then
        begin
          for iTimer := 0 to Sections.Count - 1 do
            begin
              New(DataTimer);
              DataTimer.hTimer := CreateWaitableTimer(nil, false, nil);
              DateTimeToSystemTime(StrToDateTime(Sections[iTimer]), sysTime);
              SystemTimeToFileTime(sysTime, fTime);
              LocalFileTimeToFileTime(fTime, fTime);
              DataTimer.StrDateTime := PChar(Sections[iTimer]);
              SetWaitableTimer(DataTimer.hTimer, TLargeInteger(fTime),
                               Round(Frac(StrToDateTime(ReadString(Sections[iTimer], 'Period', '0'))) * 86400) * 1000,
                               @TimerAPCProc, DataTimer, false);
              dta.Add(DataTimer);
            end;
        end;
    finally
      Free;
    end;
end;
procedure UninstallTimers;
var
  iTimer: integer;
  hTimer: THandle;
begin
  for iTimer := 0 to dta.Count - 1 do
    begin
      hTimer := PDataTimer(dta.Items[iTimer]).hTimer;
      CancelWaitableTimer(hTimer);
      CloseHandle(hTimer);
      Dispose(dta.Items[iTimer]);
    end;
  dta.Clear;
  Sections.Clear;
end;

...

begin
  dta := TList.Create;
  StrFilePath := GetStrFilePath;
...
  Sections.Free;
  dta.Free;
end.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)