Что такое TExternalThread? «Не удается завершить созданный извне поток» при завершении работы таймера на основе потока - PullRequest
12 голосов
/ 08 февраля 2010

Это происходит в половине случаев при закрытии моего приложения, в котором я поместил TLMDHiTimer на мою форму во время разработки, для параметра Enabled установлено значение true. В моем событии OnFormClose я вызываю MyLMDHiTimer.Enabled: = false. Когда это вызывается, я иногда (примерно в половине случаев) получаю это исключение.

Я отладил и вошел в вызов, и обнаружил, что это строка 246 в LMDTimer.pas, которая выдает эту ошибку.

FThread.Terminate;

Я использую последнюю версию LMDTools. Перед выходными я выполнил полную переустановку инструментов LMD, а также правильно удалил и снова добавил компонент в форму.

Из того, что я нашел, это как-то связано с TExternalThread, но от Embarcadero нет документации по нему, и я не нашел ничего ссылающегося на него в исходном коде LMDTools.

Использование полностью обновленной RAD Studio 2010, Delphi 2010.

Что меня действительно огорчает, так это отсутствие документации вообще. Google выдает один результат, который на самом деле говорит об этом, в котором кто-то говорит, что ошибка вызвана попыткой завершить TExternalThread. Но, глядя на исходный код этого LMDHiTimer, он ни разу не пытался что-либо сделать, кроме создания обычного TThread. Один результат Google, который я смог найти, Тема: Не удается завершить созданную извне тему? в Embarcadero упоминает об использовании GetCurrentThread () и GetCurrentThreadId () для получения данных, необходимых для подключения к существующему потоку, но TLMDHiTimer не делает таких вещей. Он просто создает своего собственного потомка TThread со своим собственным конструктором Create () (переопределенным, конечно, и вызовами, унаследованными в начале конструктора)

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

Заранее благодарю за любую помощь.

Ответы [ 4 ]

12 голосов
/ 08 февраля 2010

TExternalThread оборачивает поток, который Delphi RTL не создал. Он может представлять поток, принадлежащий пулу потоков ОС, или может быть поток, созданный другой DLL в вашей программе. Поскольку поток выполняет код, который не принадлежит связанному классу TExternalThread, метод Terminate не может уведомить поток о том, что вы хотите его остановить.

Объект Delphi TThread установит для своего свойства Termination значение True, и ожидается, что перезаписанный метод Execute будет периодически проверять это свойство, но, поскольку этот поток не является кодом Delphi, метод Execute отсутствует, и любой Termination свойство появилось только после код потока был уже написан где-то еще (не переопределяя Execute).

В ветке новостной группы показано, что, вероятно, происходит в вашем случае:

... у вас повреждена память, из-за которой элемент TThread.FExternalThread становится ненулевым значением.

Это может быть связано с ошибкой в ​​библиотеке компонентов или с ошибкой в ​​вашем собственном коде. Вы можете использовать точки останова данных отладчика , чтобы попытаться выяснить это. Установите точку останова в конструкторе потока таймера. Когда ваша программа делает паузу там, используйте команду «Добавить точку останова» в меню «Выполнить», чтобы добавить точку останова данных, используя адрес поля FExternalThread нового объекта. После этого, если значение этого поля изменится, отладчик остановится и покажет вам, что его изменило. (Точка останова данных будет сбрасываться при каждом запуске программы, поскольку в среде IDE предполагается, что объект не будет выделяться по одному и тому же адресу каждый раз.)

5 голосов
/ 07 июня 2011

Есть ли вероятность, что код пытается завершить уже уничтоженный TThread?Это может легко произойти, если у вас установлен FreeOnTerminate.

Я заметил ваше сообщение, когда диагностировал аналогичную (противоположную?) Ошибку: «Невозможно вызвать запуск в запущенном или приостановленном потоке» в конструкторе компонента, размещенного наОсновная форма.Когда я удалил Start (), эта ошибка была заменена более значительными ошибками, например, «недопустимая операция с указателем» и «нарушение доступа» в соответствующем деструкторе.Компонент пытался манипулировать своим объектом TThread после того, как TThread был освобожден, таким образом оставляя вещи в соответствии с законом Мерфи.Когда я это исправил, я смог заменить вызов Start (), не возвращая ошибку «Cannot call Start».

По аналогии, может ли ваша проблема заключаться в том, что адрес вашего FExternalThread был переделан и забит раньше?Деструктор / Завершить вызов?В нашем случае у нас была ошибочная реализация Singleton Instance Pattern ;но опять же, FreeOnTerminate также кажется вероятным подозреваемым.

[К вашему сведению: я использую C ++ под RAD Studio XE]

0 голосов
/ 20 июня 2012

Эта та же самая проблема сводила меня с ума в течение нескольких месяцев. Я подробно просмотрел свой код потока, пока почти не ослеп.

Наконец, я купил хорошую систему исключений (EurekaLog), и это привело меня непосредственно к стороннему компоненту, который вызывал проблему. Этот компонент, TAbLED, имеет свойство, чтобы он мог мигать с каким-то потоком синхронизации. Устранение проблемы с флэш-памятью решило проблему.

0 голосов
/ 23 февраля 2011

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

...