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


Автор: crYon 14.5.2008, 22:54
Здравствуйте.
У меня наблюдается чрезвычайно странный глюк.
Вот простейший код в Билдере (форма с таймером):

Код

static DWORD TStart=GetTickCount();
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
  Canvas->TextOut(0,0, ::GetTickCount() - TStart );
}
//---------------------------------------------------------------------------


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

Подскажите, пожалуйста, в чем может быть проблема, у меня голова уже не варит.

Автор: xvr 15.5.2008, 12:06
На какой интервал настроен Timer1? При малых интервалах так вполне может быть

Автор: crYon 15.5.2008, 19:38
Не важно, на какой. Например на 1000 или 100 мс. А почему на малых вдруг такое может быть?

Автор: xvr 15.5.2008, 19:46
Цитата(crYon @ 15.5.2008,  19:38)
Не важно, на какой. Например на 1000 или 100 мс. А почему на малых вдруг такое может быть?

Event'ы от таймера приходят в виде сообщений через общую очередь сообщений. Если интервал маленький или в цикле обработки сообщений есть какие то процедуры занимающие много времени, то event'ы от таймера будут накапливаться в очереди и потом отрабатываться пачками.

Автор: Artemon 16.5.2008, 06:16
Если хотите чтобы было все ровно, то запустите поток, а в нем пытайтесь отсчитывать время, если на экран не будете выводить, то тормозить не будет, при выводе на экран опять могут начаться такие косяки, т.к. придется использовать Synchronize.

Автор: mrbrooks 16.5.2008, 07:49
Во первых Timer не рекомендуется использовать на интервале меньше 50.
Во вторых лучше явно приводить к типу указанному в сигнатуре функции. Не стоит уповать на приведение типов Бормана по умолчанию.
Код

static DWORD TStart=GetTickCount();
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
  Canvas->TextOut(0,0, String(GetTickCount() - TStart) );
}
//---------------------------------------------------------------------------

В третьих что такого ужасного выводить данные в компонент - типа метки или statictext и забить на канву?

Автор: crYon 16.5.2008, 09:09
Цитата(xvr @  15.5.2008,  19:46 Найти цитируемый пост)
event'ы от таймера будут накапливаться в очереди и потом отрабатываться пачками

сообщения WM_TIMER не будут накапливаться, т.к. они запихиваются в очередь только при отсутствии в ней других сообщений. В любом случае, это не причина моей проблемы, т.к. возобновление обработки сообщений вызывало бы скачок счетчика, а, как я уже писал, его нет.
Цитата(Artemon @  16.5.2008,  06:16 Найти цитируемый пост)
Если хотите чтобы было все ровно

И так должно быть все равно, без использования потоков и синхронизации. Ведь нет привязки к количеству вызовов обработчика таймера. Прошедшее время узнается уже в самом обработчике. А как по Вашему, если я сделаю все как Вы сказали, такого не будет? Вряд ли. Мне ведь все равно придется использовать GetTickCount (ну или GetSystemTime/GetLocalTime), а похоже, что причина именно в них.
Цитата(mrbrooks @  16.5.2008,  07:49 Найти цитируемый пост)
лучше явно приводить к типу указанному в сигнатуре функции

Абсолютно ни чем не лучше, если ты знаком со всеми ее перегруженными вариантами. В данном случае их вообще нет. Тогда уж не String, а AnsiString. И, самое интересное, кто такой Борман?
Цитата(mrbrooks @  16.5.2008,  07:49 Найти цитируемый пост)
В третьих что такого ужасного выводить данные в компонент - типа метки или statictext и забить на канву?

Абсолютно ничего ужасного. Я уже перепробовал по всякому. Причина не в этом, причина в ф-ии GetTickCount. Я не использовал лэйблоподобный контрол в примере только потому, чтобы Вам было легче воспроизвести пример.

Автор: mrbrooks 16.5.2008, 09:23
Борман шутливое название Борланда.
А теперь объясни мне разницу в билдере между String и AnsiString?

Автор: xvr 16.5.2008, 12:00
Цитата(crYon @ 16.5.2008,  09:09)
Цитата(xvr @  15.5.2008,  19:46 Найти цитируемый пост)
event'ы от таймера будут накапливаться в очереди и потом отрабатываться пачками

сообщения WM_TIMER не будут накапливаться, т.к. они запихиваются в очередь только при отсутствии в ней других сообщений. 

Не совсем:
Цитата

The WM_TIMER message is a low-priority message. The GetMessage and PeekMessage functions post this message only when no other higher-priority messages are in the thread's message queue. 
Т.е. WM_TIMER все же могут накапливаться.
Цитата

В любом случае, это не причина моей проблемы, т.к. возобновление обработки сообщений вызывало бы скачок счетчика, а, как я уже писал, его нет.
Точно нет? Визуально его можно и не увидеть. Проверь значение, которое будет выведено через пару минут после старта программы (по часам).

Автор: Artemon 16.5.2008, 14:55
Цитата

И так должно быть все равно, без использования потоков и синхронизации. Ведь нет привязки к количеству вызовов обработчика таймера. Прошедшее время узнается уже в самом обработчике. А как по Вашему, если я сделаю все как Вы сказали, такого не будет? Вряд ли. Мне ведь все равно придется использовать GetTickCount (ну или GetSystemTime/GetLocalTime), а похоже, что причина именно в них.


Если причина в GetTickCount то не поможет,

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

Автор: crYon 16.5.2008, 15:13
Цитата(mrbrooks @  16.5.2008,  09:23 Найти цитируемый пост)
А теперь объясни мне разницу в билдере между String и AnsiString?

Никакой. Просто я так понял, что ты хочешь следовать сигнатуре функции.

Цитата(xvr @  16.5.2008,  12:00 Найти цитируемый пост)
Не совсем:
Цитата

The WM_TIMER message is a low-priority message. The GetMessage and PeekMessage functions post this message only when no other higher-priority messages are in the thread's message queue. 

Т.е. WM_TIMER все же могут накапливаться.


Цитата(Borland C++Builder Help)

WM_TIMER
The DispatchMessage function forwards this message when no other messages are in the thread's message queue.

А VCL использует DispatchMessage всегда. Правда эту фразу тоже можно понять двояко, что имеется в виду под "другими сообщениями" - любые сообщения, уже содержащиеся в очереди, или сообщения, отличающиеся от WM_TIMER.

Добавлено через 2 минуты и 33 секунды
Провел несколько тестов.
Результаты очень интересные.

Запустил прогу и сверял по механическому секундомеру.
После 5 минут задержка составила ~ 25 секунд.
После 10 минут задержка составила ~ 48 секунд.

Запустил два экземпляра проги (в разное время). Обе проги тормозили абсолютно синхронно. Т.е. счетчик останавливался и снова запускался в обоих прогах одновременно, хотя запущены они были в разное время. Отсюда я опять делаю вывод, что тормозят именно системные часы. Но, повторюсь, часы в трее идут исправно. Стало быть, они используют другие механизмы, отличные от вызовов GetTickCount/GetSystemTime/GetLocalTime.
Все никак не могу проверить на другом компьютере. Кто-нибудь проверял данный код? Ни у кого больше задержки не было?

Я либо скоро стану идиотом, либо поверю в колдовство, либо раскрою страшный баг Windows. Или это будет баг Матрицы. А может это будет парадокс, который повлечет за собой цепную реакцию и разрушение вселенной...

Автор: crYon 16.5.2008, 20:13
Попробовал без использования VCL, на WinAPI.


Код

//---------------------------------------------------------------------------
#include <windows.h>
#include <stdio.h>
//---------------------------------------------------------------------------

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);

WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
  WNDCLASSEX WndClassEx;
  WndClassEx.cbSize          = sizeof(WNDCLASSEX);
  WndClassEx.style           = CS_HREDRAW | CS_VREDRAW;
  WndClassEx.lpfnWndProc     = WndProc;
  WndClassEx.cbClsExtra      = 0;
  WndClassEx.cbWndExtra      = 0;
  WndClassEx.hInstance       = hInstance;
  WndClassEx.hIcon           = LoadIcon(NULL, IDI_QUESTION);
  WndClassEx.hCursor         = LoadCursor(NULL, IDC_ARROW);
  WndClassEx.hbrBackground   = (HBRUSH)COLOR_WINDOW;
  WndClassEx.lpszMenuName    = NULL;
  WndClassEx.lpszClassName   = "XXXWindow";
  WndClassEx.hIconSm      = LoadIcon(NULL, IDI_QUESTION);
  if(!RegisterClassEx(&WndClassEx)) return -1;
  HWND FMain = CreateWindow("XXXWindow", "Weird thing", WS_OVERLAPPEDWINDOW,
               CW_USEDEFAULT, 0, 200, 80, NULL, NULL, hInstance, NULL);
  if(FMain == NULL) return -1;
  ShowWindow(FMain, SW_SHOW);
  UpdateWindow(FMain);

  SetTimer(FMain, 1, 100, NULL);

  MSG  msg;
  int status;
  while ((status = GetMessage(&msg, 0, 0, 0)) != 0)
  {
    if (status == -1) return status;
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}
//---------------------------------------------------------------------------
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
  volatile static DWORD Count = 0, TStart = GetTickCount();

  switch(Message)
  {
    case WM_CREATE:
      return 0;
    case WM_TIMER:
    {
      static char Message[100];
      sprintf(Message,"count = %d, dt = %d",++Count,GetTickCount()-TStart);
      HDC hdc = GetDC(hwnd);
      TextOut(hdc,0,0,Message,strlen(Message));
      ReleaseDC(hwnd,hdc);
      break;
    }
    case WM_CLOSE:
      DestroyWindow(hwnd);
      KillTimer(hwnd,1);
      return 0;
    case WM_DESTROY:
      PostQuitMessage(0);
      return FALSE;
  }
  return DefWindowProc(hwnd, Message, wParam, lParam);
}
//---------------------------------------------------------------------------



Та же самая история... 

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