Delphi Win API CreateTimerQueueTimer темы и потокобезопасные сбои FormatDateTime - PullRequest
4 голосов
/ 09 декабря 2008

Это немного длинный вопрос, но здесь мы идем. Существует версия FormatDateTime, которая называется поточно-ориентированной, поскольку вы используете

GetLocaleFormatSettings(3081, FormatSettings); 

чтобы получить значение, а затем вы можете использовать его вот так;

FormatDateTime('yyyy', 0, FormatSettings); 

Теперь представьте два таймера, один из которых использует TTimer (скажем, интервал 1000 мс), а затем другой таймер, созданный так (интервал 10 мс);

CreateTimerQueueTimer
(
  FQueueTimer, 
  0, 
  TimerCallback, 
  nil, 
  10, 
  10, 
  WT_EXECUTEINTIMERTHREAD
);

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

for i := 1 to 10000 do
begin
  FormatDateTime('yyyy', 0, FormatSettings);
end;

Обратите внимание, что назначения нет. Это приводит к нарушениям доступа почти сразу, иногда через 20 минут, что угодно, в произвольных местах. Теперь, если вы пишете этот код в C ++ Builder, он никогда не падает. Мы используем преобразование заголовков JEDI JwaXXXX. Даже если мы поместим блокировки в версию Delphi вокруг кода, это только задержит неизбежное. Мы рассмотрели исходные файлы заголовков C и все выглядит хорошо, есть ли другой способ использования C ++ среды выполнения Delphi? Потокобезопасная версия FormatDatTime выглядит повторно входящей. Любые идеи или мысли от любого, кто, возможно, видел это раньше.

UPDATE:

Чтобы немного сузить это, FormatSettings передается как const, так имеет ли значение, если они используют одну и ту же копию (как оказалось, передача локальной версии в вызове функции приводит к той же проблеме)? Также версия FormatDateTime, которая принимает FormatSettings, не вызывает GetThreadLocale, потому что она уже имеет информацию о Locale в структуре FormatSettings (я дважды проверил, шагая по коду).

Я упомянул об отсутствии назначения, чтобы было ясно, что к общему хранилищу не обращаются, поэтому блокировка не требуется.

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

Если вы используете простой старый TThread, проблема не возникает. Я предполагаю, что использование TThread или TTimer работает, но использование потока, созданного вне VCL, не работает, поэтому я спросил, есть ли разница в том, как C ++ Builder использует VCL / Delphi RTL.

Кроме этого, упомянутый выше код также не работает (но занимает больше времени), через некоторое время CS: = TCriticalSection.Create;

  CS.Acquire;
  for i := 1 to LoopCount do
  begin
    FormatDateTime('yyyy', 0, FormatSettings);
  end;
  CS.Release;

А теперь я действительно не понимаю, я написал это как предложено;

function ReturnAString: string;
begin
  Result := 'Test';
  UniqueString(Result);
end;

, а затем внутри каждого типа таймера код:

  for i := 1 to 10000 do
  begin
    ReturnAString;
  end;

Это вызывает те же виды сбоев, как я уже говорил ранее, ошибка никогда не находится в одном и том же месте внутри окна ЦП и т. Д. Иногда это нарушение доступа, а иногда это может быть недопустимая операция с указателем. Я использую Delphi 2009 между прочим.

ОБНОВЛЕНИЕ 2:

Родди (ниже) указывает на событие Ontimer (и, к сожалению, также Winsock, то есть TClientSocket) использует насос сообщений Windows (кроме того, было бы неплохо иметь несколько хороших компонентов Winsock2, использующих IOCP и Overlapping IO), следовательно, push уйти от этого. Однако кто-нибудь знает, как узнать, какой тип локального хранилища потока настроен в CreateQueueTimerQueue?

Спасибо, что нашли время подумать и ответить на эту проблему.

Ответы [ 8 ]

5 голосов
/ 10 декабря 2008

Я не уверен, стоит ли публиковать «Ответ» на мой вопрос, но это кажется логичным, дайте мне знать, если это не круто.

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

IsMultiThread: = True;

Из справки;

"IsMultiThread имеет значение true, чтобы указать, что диспетчер памяти должен поддерживать несколько потоков. IsMultiThread имеет значение true для BeginThread и фабрики классов."

Это, конечно, не , установленный с использованием одного основного потока VCL с использованием TTimer, однако он устанавливается при использовании TThread. Если я установлю его вручную, проблема исчезнет.

В C ++ Builder я не использую TThread, но он появляется с помощью следующего кода;

if (IsMultiThread) {
  ShowMessage("IsMultiThread is True!");
}

то есть он для вас где-то установлен автоматически.

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

1 голос
/ 09 декабря 2008

Вы уверены, что это как-то связано с FormatDateTime? Вы упомянули, что там нет оператора присваивания; это важный аспект вашего вопроса? Что произойдет, если вы вызовете какую-то другую функцию, возвращающую строку? (Убедитесь, что это не постоянная строка. Напишите свою собственную функцию, которая вызывает UniqueString(Result) перед возвратом.)

Является ли переменная FormatSettings специфичной для потока? В этом и заключается смысл иметь дополнительный параметр для FormatDateTime, поэтому каждый поток имеет свою собственную личную копию, которая гарантированно не будет изменена никаким другим потоком, пока функция активна.

Важна ли очередь таймера для этого вопроса? Или вы получаете те же результаты, когда используете простой старый TThread и запускаете цикл в методе Execute?

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

1 голос
/ 09 декабря 2008

Поскольку DateTimeToString, вызовы FormatDateTime которого используют GetThreadLocale, вы можете попробовать использовать локальную переменную FormatSettings для каждого потока, возможно, даже настроить FormatSettings в локальной переменной перед циклом.

Это также может быть параметр WT_EXECUTEINTIMERTHREAD, который вызывает это. Обратите внимание, что в нем говорится, что его следует использовать только для очень коротких задач.

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

Сведения о том, где происходит нарушение доступа, могут помочь.

0 голосов
/ 13 января 2009

Для обновления2:

Есть бесплатные компоненты сокета IOCP: http://www.torry.net/authorsmore.php?id=7131 (с исходным кодом)

"Набережных Сергей Николаевич. Высокий сервер сокетов производительности на основе Порт завершения Windows и с использованием Расширения Windows Socket. IPv6 поддерживается. «

Я нашел это, когда искал лучшие компоненты / библиотеку, чтобы перестроить мой маленький сервер обмена мгновенными сообщениями. Я еще не пробовал, но это выглядит хорошо закодировано как первое впечатление.

0 голосов
/ 10 декабря 2008

Проблема с Indy в том, что, если вам нужно много соединений, это совсем не эффективно. Для каждого соединения требуется один поток (блокирующий ввод / вывод), который вообще не масштабируется, поэтому преимущества IOCP и Overlapping IO - это практически единственный масштабируемый способ в Windows.

0 голосов
/ 10 декабря 2008

Re. Ваш последний вопрос о Winsock и перекрывающихся операциях ввода-вывода: вам следует присмотреться к Indy .

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

0 голосов
/ 10 декабря 2008

Вы нашли свой ответ - IsMultiThread. Это должно быть использовано в любое время, чтобы вернуться к использованию API и создавать потоки. Из MSDN: CreateTimerQueueTimer создает пул потоков для обработки этой функциональности, поэтому у вас есть внешний поток, работающий с основным потоком VCL без защиты. (Примечание: ваш CS.acquire / release вообще ничего не делает, если другие части кода не учитывают эту блокировку.)

0 голосов
/ 10 декабря 2008

Интересно, ожидают ли выполняемые вами вызовы RTL / VCL доступа к некоторым переменным локального хранилища (TLS), которые неправильно настроены при вызове кода через очередь таймера?

Это не решение вашей проблемы, но знаете ли вы, что события TTimer OnTimer просто выполняются как часть обычного цикла сообщений в главном потоке VCL?

...