Некоторая помощь с TThread (Terminate, FreeOnTerminate и другие приключения в области многопоточности)

Я пытаюсь добиться следующего (используя Delphi7): после входа в мою программу пользователь получает контроль, но в фоновом режиме отдельный поток загружает файл из Интернета, чтобы проверить, не занесен ли текущий лицензионный ключ в черный список. Если это так, пользователь получает приглашение, и программа завершает работу.

Поэтому я создал отдельный класс TThread, который загружает черный список из Сети, используя InternetOpenURL/InternetReadFile.

Моя проблема заключается в следующем:

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

Если поток выполнил свою работу, он должен завершиться автоматически.

Если я использую FreeOnTerminate := true, я не могу завершить поток из основного потока. Но иначе, как я могу освободить поток после того, как он выполнил свою работу?

Еще один вопрос:

Если лицензионный ключ занесен в черный список, я использую Synchronize, чтобы сделать что-то с определенными ресурсами основной формы Приложения.

Но как узнать, что пользователь уже закрыл приложение и программа находится в FormDestroy главной формы, например? Если я синхронизирую в неподходящее время, это может привести к нарушению прав доступа...

Спасибо!


person Steve    schedule 13.08.2010    source источник
comment
Просто любопытно ... почему вы делаете это, а не просто отправляете лицензионный ключ, скажем, в веб-приложение и получаете ответ «да / нет» для проверки ключа?   -  person GrandmasterB    schedule 14.08.2010
comment
Я делаю это прямо сейчас. Я вызываю php-скрипт с лицензионным ключом в качестве параметра, и если он занесен в черный список, он возвращает строку BLACKLISTED и причину занесения в черный список (клиент не оплатил счет и т. д.). Я также использую тот же скрипт/ часть программы по выпуску обновлений лицензии (пользователь продлевает подписку на программное обеспечение)   -  person Steve    schedule 14.08.2010
comment
Проще говоря, если вы хотите каким-либо образом взаимодействовать с потоком (включая WaitFor или Terminate при особых условиях), то FreeOnTerminate не подходит для этой работы. FreeOnTerminate — это ярлык, который вы используете для выстрелил-забыл. Вы можете создать TSimpleEvent, чтобы отслеживать, когда/если поток действительно уничтожен, и позволить вашему приложению WaitFor сделать это. Но тогда вы могли бы просто уничтожить тему самостоятельно с помощью: Thread.Terminate; Thread.WaitFor(<timeout>); Thread.Free;.   -  person Disillusioned    schedule 24.10.2014


Ответы (3)


Я думаю, это то, что вы пытаетесь сделать... Назначьте событие OnTerminate() для TThread, которое выполняется как часть вашего основного потока, когда рабочий поток завершается. Вы можете делать все обновления вашего пользовательского интерфейса там (черный список). Создайте свойство своего рабочего потока, такое как «DownloadComplete», и выполняйте черный список/проверку только в том случае, если в событии OnTerminated установлено значение true. Это должно позволить потоку освободиться независимо от состояния загрузки при запуске программы, если вы .watifor() перед выходом из программы.

person GrandmasterB    schedule 13.08.2010
comment
Но таким образом OnTerminate не будет вызываться до завершения MyThread.Execute, верно? Потому что в MyThread.Execute я загружаю файл - и что произойдет, если загрузка зависнет из-за не отвечающего сервера? MyThread.WaitFor будет ждать завершения загрузки и зависания приложения... (я этого не хочу) - person Steve; 14.08.2010
comment
Как дела с загрузкой? Вы должны иметь возможность отменить его в любой момент, проверив свойство terminated потока. Хорошая процедура загрузки должна иметь либо тайм-аут, либо обратный вызов, позволяющий прервать загрузку, если поток помечен как завершенный. - person GrandmasterB; 14.08.2010
comment
Я использую такой код: delphi.about.com/od/internetintranet/ a/get_file_net.htm (InternetOpenURL, InternetReadFile) Я думаю, что эти функции имеют значение тайм-аута, но вы не можете отменить их... - person Steve; 14.08.2010
comment
Я никогда не использовал это, но если у него его нет, я бы переключился на что-то, что допускает тайм-аут, если сетевое соединение зависает. В противном случае, потоки или нет, ваш процесс будет зависать до физического завершения. Действительно, исходя из кода, который вы используете, выполнение загрузки с использованием чтения сокетов не намного сложнее. В основном это тот же подход - откройте соединение, отправьте http GET и начните читать ответ. Вы можете использовать что-то вроде библиотеки синапсов (ararat.cz/synapse/doku.php/start) для этого. - person GrandmasterB; 14.08.2010
comment
Спасибо за предложение. Моя проблема с Synapse заключается в том, что он кажется устаревшим (нет версии Delphi2010, Delphi2009 только экспериментальная) - я бы предпочел не полагаться на какие-либо сторонние компоненты для такой простой вещи. Любые другие предложения? - person Steve; 14.08.2010
comment
Delphi имеет свой собственный класс сокетов (я думаю, он называется TClientSocket или TClientWinSocket или что-то в этом роде), который можно использовать для него, если вы хотите избежать компонента. Или вы можете напрямую использовать API-функции сокета Windows. - person GrandmasterB; 16.08.2010
comment
Я должен пометить это, потому что это в корне ошибочно. Если вы используете WaitFor() в свободном потоке при завершении, вы рискуете AV, потому что объект потока может быть уничтожен до того, как вы действительно начнете ждать. Единственный способ сделать это работоспособным — создать отдельное событие, о котором можно сигнализировать в обработчике событий OnTerminate. Затем вы можете спокойно ждать события и уничтожить его. В этом случае вы можете просто не делать свой поток FreeOnTerminate и освобождать его самостоятельно, когда он будет завершен. - person Disillusioned; 24.10.2014

Во-первых, в объекте потока проверки создайте флаг «завершено». Вы можете проверить это, чтобы определить, все ли в порядке. Как предлагает Крис Т., пусть поток устанавливает глобальное значение, чтобы указать, что все хорошо/плохо, чтобы основной поток мог использовать что-то вроде таймера, чтобы проверить, все ли в порядке, или предпринять соответствующие действия.

Затем, если ваше приложение хочет завершить работу досрочно, вызовите

  MyThread.Terminate;
  MyThread.WaitFor;

И в потоке проверьте, установлен ли Terminated в соответствующих точках. Таким образом, вы можете закрыть красиво.

person mj2008    schedule 13.08.2010
comment
Я знаю о WaitFor, но если поток завершает свою работу до закрытия программы, как он может освободить свои ресурсы (например, строки, которые он использует) без использования флага FreeOnTerminate? - person Steve; 13.08.2010

Что касается второго вопроса, не беспокойтесь об обновлении пользовательского интерфейса из потока. Просто установите глобальный флаг. то есть IsBlackListed := true; Теперь вы можете использовать этот глобальный флаг в качестве основы для придирок к пользователю в ответ на какое-либо действие, инициированное пользователем. Например, OnFileSave... ShowMessage('Я хотел бы сохранить этот файл для вас, но, к сожалению, вы используете ключ из черного списка.');

IsBlackListed Redux....

// globally...
var
  IsBlackListed  : integer;

...
initialization
  IsBlackListed  := 0;

...
// in your thread:
if OhMyGodThisIsABlackListedKey then
  IsBlackListed := 1;

... Back in the main code, maybe in the OnSaveMyData event:

if IsBlackListed then
begin
  IsBlackListed := -1; // so we don't repeat ourselves
  MessageBox('Hey, you naughty pirate!');
  MyDBConnection.Whatever.DoSQL('insert into licensetable (name,key) values(pirate,blacklisted);
end;
person Chris Thornton    schedule 13.08.2010
comment
Привет! Если ключ занесен в черный список, мне нужно обновить запись в базе данных программы, а компоненты БД находятся на главной форме - поэтому ее нужно обновить до FormDestroy. Как мне это сделать? - person Steve; 13.08.2010
comment
Пропустите компоненты БД и вставьте в черный список (имя пользователя, ключ, бла-бла) значения (бла-бла-бла); - person Chris Thornton; 13.08.2010
comment
Я уже называю это так: MainForm.IBCQuery.SQL.Text := 'INSERT INTO...'; Я не могу создать компонент IBCQuery динамически, так как не хочу устанавливать несколько подключений к базе данных. (и IBCConnection также находится на MainForm) - person Steve; 14.08.2010
comment
Тогда просто пропустите это и сделайте это в следующий раз, когда они не будут отключены. Я добавлю код в свой ответ, чтобы справиться с этим.... - person Chris Thornton; 14.08.2010
comment
Спасибо за код, но проблема в том, что как только обнаруживается ключ из черного списка, он должен МГНОВЕННО вызвать showmessage, сохранить что-то в БД и затем закрыть приложение. Для этого я могу использовать Synchronize из моего потока. Проблема в том, что если приложение завершает работу, а основной поток находится в FormDestroy, выполнение Synchronize приведет к нарушению прав доступа... Итак, как я могу узнать из своего потока, что выполнять Synchronize безопасно? - person Steve; 14.08.2010
comment
@ Стив - я уверен, что у тебя есть свои причины, но я бы предположил, что ты все делаешь неправильно. Вы видимо не согласны, я понимаю. Кстати, и FWIW, я заработал много тысяч долларов, обнаружив ключи из черного списка, используя несколько более мягкий подход и конвертируя пиратов в продажи. Но я уверен, что твой способ тоже хорош. - person Chris Thornton; 14.08.2010
comment
Крис, это не условно-бесплатное программное обеспечение. Это корпоративный софт. Они платят ежегодно, поэтому я выдаю им годовые лицензии. Но если пользователь не платит вовремя или не платит вообще, мне нужен способ отключить его доступ в течение одного года. Не хочу их пилить, просто отключаю доступ ко всей программе. - person Steve; 14.08.2010