Есть ли способ спать, если сообщение не получено? - PullRequest
3 голосов
/ 28 июля 2010

Я работаю в службе, основной цикл которой выглядит следующим образом:

while (fServer.ServerState = ssStarted) and (Self.Terminated = false) do
begin
  Self.ServiceThread.ProcessRequests(false);
  ProcessFiles;
  Sleep(3000);      
end;

ProcessRequests очень похож на Application.ProcessMessages.Я не могу передать ему true, потому что, если я это сделаю, он блокируется до тех пор, пока сообщение не будет получено из Windows, и ProcessFiles не будет работать, и он должен работать постоянно.Спящий режим предназначен для того, чтобы снизить нагрузку на процессор.

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

Итак, мне нужно сказать: «Спи 3000 или пока не получишь сообщение, в зависимости от того, что наступит раньше».Я уверен, что есть API для этого, но я не уверен, что это такое.Кто-нибудь знает?

Ответы [ 7 ]

11 голосов
/ 28 июля 2010

Подобные вещи сложно понять правильно, поэтому я обычно начинаю с документации API на MSDN.

Документ WaitForSingleObject специально указывает на MsgWaitForMultipleObjects дляследующие типы ситуаций:

Будьте осторожны при вызове функций ожидания и кода, который прямо или косвенно создает окна.Если поток создает какие-либо окна, он должен обрабатывать сообщения.Сообщения трансляций отправляются во все окна системы.Поток, использующий функцию ожидания без интервала времени ожидания, может привести к зависанию системы.Два примера кода, который косвенно создает окна, - это DDE и функция CoInitialize .Поэтому, если у вас есть поток, который создает окна, используйте MsgWaitForMultipleObjects или MsgWaitForMultipleObjectsEx , а не WaitForSingleObject.

In MsgWaitForMultipleObject*, у вас есть параметр dwWakeMask, указывающий, какие сообщения в очереди следует возвращать, и таблицу, описывающую маски, которые вы можете использовать.

Редактировать из-за комментария Уоррена П :

Если ваш основной цикл может быть продолжен из-за ReadFileEx , WriteFileEx или QueueUserAPC , тогда вы можете использовать SleepEx .

- jeroen

8 голосов
/ 28 июля 2010

MsgWaitForMultipleObjects () - это путь, то есть:

while (fServer.ServerState = ssStarted) and (not Self.Terminated) do 
begin 
  ProcessFiles; 
  if MsgWaitForMultipleObjects(0, nil, FALSE, 3000, QS_ALLINPUT) = WAIT_OBJECT_0 then
    Self.ServiceThread.ProcessRequests(false); 
end;

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

var
  iDue: TLargeInteger;
  hTimer: array[0..0] of THandle;
begin
  iDue := -30000000; // 3 second relative interval, specified in nanoseconds
  hTimer[0] := CreateWaitableTimer(nil, False, nil);
  SetWaitableTimer(hTimer[0], iDue, 0, nil, nil, False);
  while (fServer.ServerState = ssStarted) and (not Self.Terminated) do 
  begin 
    // using a timeout interval so the loop conditions can still be checked periodically
    case MsgWaitForMultipleObjects(1, hTimer, False, 1000, QS_ALLINPUT) of
      WAIT_OBJECT_0:
      begin
        ProcessFiles;
        SetWaitableTimer(hTimer[0], iDue, 0, nil, nil, False);
      end;
      WAIT_OBJECT_0+1: Self.ServiceThread.ProcessRequests(false);
    end;
  end;
  CancelWaitableTimer(hTimer[0]);
  CloseHandle(hTimer[0]);
end;
2 голосов
/ 28 июля 2010

Используйте таймер для запуска ProcessFiles вместо взлома его в главном цикле приложения. Затем ProcessFiles будет запускаться в нужный интервал, и сообщения будут обрабатываться правильно, без использования 100% ЦП.

1 голос
/ 28 июля 2010

Я использовал TTimer в многопоточном приложении со странными результатами, поэтому теперь я использую Events.

while (fServer.ServerState = ssStarted) and (Self.Terminated = false) do
begin
  Self.ServiceThread.ProcessRequests(false);
  ProcessFiles;

  if ExitEvent.WaitFor(3000) <> wrTimeout then
    Exit;   
 end;

Вы создаете событие с

ExitEvent := TEvent.Create(nil, False, False, '');

Теперь самое последнее - запустить событие в случае остановки службы. Я думаю, что событие Stop службы - правильное место, чтобы поставить это.

ExitEvent.SetEvent;

Я использую этот код для очистки потока в моей системе пулов подключений к БД, но он должен хорошо работать и в вашем случае.

0 голосов
/ 29 июля 2010

Разве невозможно переместить ProcessFiles в отдельный поток? В вашем MainThread вы просто ждете сообщений, а когда служба завершается, вы завершаете поток ProcessFiles.

0 голосов
/ 28 июля 2010

Используйте TEvent, который вы сигнализируете, когда поток должен проснуться.Затем заблокируйте событие (используя waitformultiple, как говорит Jeroen, если у вас есть несколько событий для ожидания)

0 голосов
/ 28 июля 2010

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

При этом ваш код может выиграть от некоторого рефакторинга. Вы говорите, что не хотите, чтобы ProcessRequests блокировали ожидание сообщения?это ProcessFiles. Если это зависит от обрабатываемого сообщения, то почему оно не может блокироваться? А если оно не зависит от обрабатываемого сообщения, то может ли оно быть разделено на другой поток? (предыдущее предложение запуска ProcessFiles через таймер:отличное предложение о том, как это сделать).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...