Я только что узнал, что создание и использование Vcl.TTimer
из рабочего потока не является потокобезопасным. Мой рабочий поток работает нормально «большую часть времени», используя Vcl.TTimer
. Я нашел класс TTimerThread
, основанный на событиях, который тоже работает нормально, но я не знаю, как объединить оба класса, которые TTimerThread
имеют доступ к методам моего рабочего потока.
ExecuteTimed()
будет вызван при возникновении события (3000 мс).
Псевдокод:
uses TimerThread;
type
TMyTimerThread = class( TTimerThread )
protected
procedure ExecuteTimed; override;
[...]
TMQTTClient = class(TThread)
private
[...]
fKeepAliveTimer:TTimer; // Works, but is ugly.
fKeepAliveTimerThread:TMyTimerThread; //
[...]
procedure TMyTimerThread.ExecuteTimed;
begin
beep; // Works fine!
end;
[...]
constructor TMQTTClient.Create(aHostname: string; aPort: integer; ClientID:String;aKeepAliveSeconds : word);
begin
//init some stuff...
fKeepAliveTimer:=TTimer.Create(nil); // ugly
fKeepAliveTimer.Interval:=fKeepAliveSeconds * 1000;
fKeepAliveTimer.OnTimer:=DoKeepAlive; // The function pointer which should be called ~ every xxxx ms. Works fine, but no thread safe.
fKeepAliveTimerThread:=TMyTimerThread.Create();
fKeepAliveTimerThread.Interval:=3000;
//fKeepAliveTimerThread.ExecuteTimed := ?; //here I wish to assign a pointer to the ExecuteTimed method of MyTimerThread.
Я мог бы представить решение этой проблемы, используя TThread.Synchronize()
в ExecuteTimed()
, который вызывает экземпляр рабочего потока, например frmMain.myMqttWorkerClass.DoKeepAlive
, но я сомневаюсь, что это правильный путь.
TThread.Synchronize()
фактически используется для выполнения кода в основном потоке.
TTimerThread
действительно имеет вызов criticalSection
, и это работает. Это скорее базовый вопрос о том, как определить TMqttClient
, что событие TTimerThread
произошло.
Вы рассматривали TThread.Sleep
метод или GetTime
опрос?
@Stas Нет. Sleep(60000) и опросы тоже кажутся неприятными.
Подробно рабочий поток обрабатывает связь по протоколу mqtt. Протоколу необходимо отправлять сообщение поддержания активности каждые xx секунд. Ранее я решил это с помощью TTimer, который, как я узнал, не является потокобезопасным. Просто хочу сделать это лучше
Вы можете добавить публичное событие в TMyTimerThread
и TMQTTClient
назначить ему обработчик DoKeepAlive
, точно так же, как вы это сделали с TTimer
, например:
type
TMyTimerThread = class(TTimerThread)
private
fOnTimer: TNotifyEvent;
protected
procedure ExecuteTimed; override;
public
property OnTimer: TNotifyEvent read fOnTimer write fOnTimer;
end;
TMQTTClient = class(TThread)
private
[...]
fKeepAliveTimerThread: TMyTimerThread;
procedure DoKeepAlive(Sender: TObject);
[...]
end;
...
procedure TMyTimerThread.ExecuteTimed;
begin
if Assigned(fOnTimer) then fOnTimer(Self);
end;
...
constructor TMQTTClient.Create(aHostname: string; aPort: integer; ClientID:String;aKeepAliveSeconds : word);
begin
//init some stuff...
fKeepAliveTimerThread := TMyTimerThread.Create();
fKeepAliveTimerThread.Interval := 3000;
fKeepAliveTimerThread.OnTimer := DoKeepAlive;
end;
Просто имейте в виду, что DoKeepAlive
будет вызываться в контексте потока таймера, поэтому убедитесь, что все, что он делает, является потокобезопасным.
Да, использование TNotifyEvent кажется правильным. DoKeepAlive() просто проверяет, подключен ли TCPClient.Connected, выполняет некоторые действия в ТБ и, наконец, вызывает FTCPClient.IOHandler.write(). Думаю, это безопасно
Работает как шарм! :) Спасибо, Реми!
@SaschaOtt Connected
выполняет операцию чтения, поэтому с ней нужно быть очень осторожным, если у вас есть другие потоки, которые также читают из того же сокета. То же самое и с Write
, если уж на то пошло. Поток может безопасно записывать, пока другой поток читает, но если 2+ потока выполняют чтение или 2+ потока выполняют запись, вам необходимо соответствующим образом синхронизировать доступ к сокету. Если TMQTTClient.Execute
что-то пишет, то я бы предложил позволить TMQTTClient.Execute
выполнять ВСЕ операции чтения и записи. Попросите DoKeepAlive
поместить уведомление в очередь, которая TMQTTClient.Execute
опрашивает и записывает при необходимости.
Действительно TMQTTClient.Execute
пишет/читает в/из сокета. Фактически все операции чтения/записи выполняются там с использованием сигнальной переменной. За исключением сообщения о сохранении активности, которое запускается таймером. Спасибо за подсказку.
@SaschaOtt Тогда может иметь смысл, чтобы таймер сигнализировал о другой переменной, которая Execute
просматривает, а затем записывает сообщение о сохранении активности при получении сигнала
Возможно, вам следует немного объяснить вашу общую цель. Я не знаком с
TTimerThread
, но думаю, можно предположить, чтоExecuteTimed
иExecute
вашегоTMQTTClient
потока являются разными потоками, поэтому, если вы хотите получить доступ к одним и тем же структурам данных из обоих, вам понадобится какая-то блокировка синхронизации, например. критические разделы, чтобы защитить доступ к общим ресурсам.