Delphi Win API CreateTimerQueueTimer потоки и потокобезопасные сбои FormatDateTime
Это немного длинный вопрос, но здесь мы идем. Существует версия 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 выглядит повторно входящей. Любые идеи или мысли от тех, кто, возможно, видел это раньше.
ОБНОВИТЬ:
Чтобы немного сузить это, 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), отсюда и толчок, чтобы уйти от него. Однако кто-нибудь знает, как увидеть, какой тип локального хранилища потока настроен на CreateQueueTimerQueue?
Спасибо, что нашли время подумать и ответить на эту проблему.
8 ответов
Я не уверен, стоит ли публиковать "Ответ" на мой вопрос, но это кажется логичным, дайте мне знать, если это не круто.
Я думаю, что нашел проблему, идея локального хранилища потоков подтолкнула меня к тому, чтобы следовать за множеством подсказок, и я нашел эту волшебную линию;
IsMultiThread: = True;
С помощью;
"IsMultiThread имеет значение true, чтобы указать, что диспетчер памяти должен поддерживать несколько потоков. IsMultiThread имеет значение true для BeginThread и фабрик классов".
Это, конечно, не устанавливается с помощью одного основного потока VCL с использованием TTimer, однако оно устанавливается для вас при использовании TThread. Если я установлю это вручную, проблема исчезнет.
В C++Builder я не использую TThread, но он появляется с помощью следующего кода;
if (IsMultiThread) {
ShowMessage("IsMultiThread is True!");
}
то есть он для вас где-то установлен автоматически.
Я очень рад за вклад людей, чтобы я мог найти это, и я надеюсь, что это может помочь кому-то еще.
Так как DateTimeToString, вызовы которого FormatDateTime использует GetThreadLocale, вы можете попробовать использовать локальную переменную FormatSettings для каждого потока, возможно, даже настроить FormatSettings в локальной переменной перед циклом.
Это также может быть параметр WT_EXECUTEINTIMERTHREAD, который вызывает это. Обратите внимание, что в нем говорится, что он должен использоваться только для очень коротких задач.
Если проблема не устранена, проблема может быть в другом месте, что было моей первой догадкой, когда я увидел это, но у меня недостаточно информации о том, что делает код, чтобы действительно определить это.
Сведения о том, где происходит нарушение доступа, могут помочь.
Вы уверены, что это как-то связано с FormatDateTime
? Вы упомянули, что там нет оператора присваивания; это важный аспект вашего вопроса? Что произойдет, если вместо этого вы вызовете какую-нибудь другую функцию, возвращающую строки? (Убедитесь, что это не постоянная строка. Напишите свою собственную функцию, которая вызывает UniqueString(Result)
до возвращения.)
Это FormatSettings
переменная для конкретного потока? Это смысл наличия дополнительного параметра для FormatDateTime
Таким образом, каждый поток имеет свою собственную частную копию, которая гарантированно не будет изменена никаким другим потоком, пока функция активна.
Важна ли для этого вопроса очередь таймера? Или вы получаете те же результаты, когда вы используете простой старый TThread
и запустить свой цикл в Execute
метод?
Вы предупреждали, что это был длинный вопрос, но, кажется, есть несколько вещей, которые вы могли бы сделать, чтобы уменьшить его, сузить суть проблемы.
Для обновления2:
Есть бесплатные компоненты сокета IOCP: http://www.torry.net/authorsmore.php?id=7131 (включая исходный код)
"Набережных Сергей Николаевич. Высокопроизводительный сокет-сервер на базе Windows Completion Port и с использованием Windows Socket Extensions. Поддерживается IPv6".
Я нашел это, когда искал лучшие компоненты / библиотеку, чтобы перестроить мой маленький сервер обмена мгновенными сообщениями. Я еще не пробовал, но это выглядит хорошо закодировано как первое впечатление.
Число рейнольдса Ваш последний вопрос о Winsock и перекрывающихся ввода / вывода: вы должны присмотреться к Indy.
Indy использует блокирующий ввод-вывод и является отличным выбором, когда вам нужен высокопроизводительный сетевой ввод-вывод независимо от того, что делает основной поток. Теперь, когда вы решили проблему многопоточности, вам нужно просто создать другой поток (или больше), чтобы использовать indy для обработки ввода-вывода.
Проблема с Indy в том, что если вам нужно много подключений, это совсем не эффективно. Для этого требуется один поток на соединение (блокирующий ввод / вывод), который не масштабируется вообще, поэтому преимущества IOCP и Overlapping IO - это практически единственный масштабируемый способ в Windows.
Вы нашли свой ответ - IsMultiThread. Это должно быть использовано в любое время, чтобы вернуться к использованию API и создавать потоки. Из MSDN: CreateTimerQueueTimer создает пул потоков для обработки этой функциональности, поэтому у вас есть внешний поток, работающий с основным потоком VCL без защиты. (Примечание: ваш CS.acquire/release вообще ничего не делает, если другие части кода не соблюдают эту блокировку.)
Интересно, ожидают ли вы при вызовах RTL/VCL доступа к некоторым переменным локального хранилища потоков (TLS), которые неправильно настроены, когда вы вызываете свой код через очередь таймера?
Это не решение вашей проблемы, но знаете ли вы, что события TTimer OnTimer просто выполняются как часть обычного цикла сообщений в основном потоке VCL?