Вызовите метод TDataModule в TThread.Execute.
В общем, возможно ли в процедуре TThread.Execute вызвать метод TDataModule, в котором не задействована визуальная активность?
Спасибо всем, Массимо.
5 ответов
Самый простой способ - использовать TThread.Synchronize для вызова метода в вашем модуле данных.
Однако, если вы не хотите этого делать, даже если нет визуальной активности, вы должны определить, нужно ли вам добавить критический раздел, чтобы защитить вас.
Любой доступ к любому стандартному или стороннему компоненту VCL, будь то визуальный (TButton) или невизуальный (наборы данных), должен рассматриваться как БЕЗОПАСНЫЙ. Любой доступ к локальному объекту данных (например, к частному полю или глобальной переменной) также должен быть защищен критическими секциями.
Вот прямой вызов из фонового потока в ваш модуль данных:
if Assigned(MyDataModule) then MyDataModule.DoSomething(a,b,c);
Вот код в вашем модуле данных, который я показываю вам пример кода, который гарантирует, что мы единственный поток, касающийся FList прямо сейчас:
/// DoSomething: Note this method must be thread-safe!
procedure TMyDataModule.DoSomething(a:TMyObject1;b:TMyObject2;c:TMyObject3);
begin
FCriticalSection.Enter;
try
if not FList.Contains(a) then
FList.Add(a);
...
finally
FCriticalSection.Leave;
end;
end;
/// elsewhere in the same data module, wherever anybody modifies or checks the state
/// (content) of FList, wrap the method with a critical section like this:
function TMyDataModule.HasItem(a:TMyObject1):Boolean;
begin
FCriticalSection.Enter;
try
result := FList.Contains(a);
finally
FCriticalSection.Leave;
end;
end;
Вот несколько начальных правил для многопоточного программирования Delphi:
- Не делайте ничего, что могло бы создать условия гонки.
- Не забывайте использовать примитивы синхронизации, такие как критические секции, мьютексы и т. Д., Для защиты от проблем параллелизма, включая условия гонки, всякий раз, когда вы обращаетесь к любым полям данных в своем классе (модуле данных) или ЛЮБОМ глобальном пространстве. Если вы используете их ненадлежащим образом, вы добавляете взаимоблокировки в свой список проблем. Так что это не хорошее место, чтобы портить.
- Если вам необходимо каким-либо образом получить доступ к компоненту или объекту VCL, сделайте это косвенно через PostMessage, TThread.Synchronize или какой-либо другой поточно-безопасный эквивалентный способ сигнализации основного потока о том, что вам нужно что-то сделать.
- Подумайте о том, что происходит, когда вы выключаетесь. Возможно, вы могли бы проверить, существует ли ваш модуль данных, так как он мог исчезнуть, прежде чем вызывать его методы.
Да, мой вопрос очень расплывчатый.
Моя программа представляет собой приложение с графической статистикой, оно должно отображать диаграмму Ганта с помощью TChart, описывая состояния, аварийные сигналы или порядки обработки одного или нескольких станков. На ПК администратора сервер (оснащенный TIdTcpServer и некоторыми компонентами БД) прослушивает мое приложение в локальной сети.
Клиент основной формы позволяет конечному пользователю выбирать диапазон дат (период) и единиц (машин) для запроса к серверу. После этого пользователь нажимает кнопку (имеется 3 функции): создается новая форма (и модуль данных) для отображения результатов.
Работа по сбору данных завершается потоком, потому что:
1) это может быть долгой работой, чтобы заморозить графический интерфейс;
2) пользователь может запустить более одной формы, чтобы увидеть различные результаты.
У меня есть базовый модуль данных (с TIdTcpClient с несколькими функциями для сбора данных), базовая форма (никогда не создается, с множеством характеристик, общих для всех форм данных, и определение рабочего потока).
unit dtmPDoxClientU;
TdtmPDoxClient = class(TDataModule)
IdTCPClient: TIdTCPClient;
...
function GetData(...): boolean;
...
end;
unit frmChartBaseFormU;
TfrmChartBaseForm = class(TForm)
...
TheThread: TThreadClient;
procedure WMThreadComm(var Message: TMessage); message WM_THREADCOMM;
procedure ListenThreadEvents(var Message: TMessage); virtual;
procedure ExecuteInThread(AThread: TThreadClient); virtual;
end;
TThreadClient = class(TThread)
private
public
Task: integer;
Module: TfrmChartBaseForm;
procedure Execute; override;
property Terminated;
end;
procedure TfrmChartBaseForm.FormCreate(Sender: TObject);
...
TheThread := TThreadClient.Create(true);
with TheThread do begin
Module := self;
FreeOnTerminate := true;
end;//with
end;//FormCreate
procedure TfrmChartBaseForm.WMThreadComm(var Message: TMessage);
begin
ListenThreadEvents(Message);
end;//WMThreadComm
procedure TfrmChartBaseForm.ListenThreadEvents(var Message: TMessage);
begin
// do override in derived classes
end;//ListenThreadEvents
procedure TfrmChartBaseForm.ExecuteInThread(AThread: TThreadClient);
begin
// do override in derived classes
end;//ExecuteInThread
procedure TThreadClient.Execute;
begin
with Module do begin
ExecuteInThread(self);
end;//with
end;//Execute
Кроме того, используя VFI, у меня также есть два блока:
unit dtmPDoxClientDataOIU;
TdtmPDoxClientDataOI = class(TdtmPDoxClient)
cdsClient_IS: TClientDataSet;
...
dsr_I: TDataSource;
...
private
public
end;
unit frmPDoxClientDataOIU;
TfrmPDoxClientDataOI = class(TfrmChartBaseForm)
ChartOI: TChart;
...
procedure FormCreate(Sender: TObject);
public
{ Public declarations }
dtmPDoxClientDataOI: TdtmPDoxClientDataOI;
procedure ListenThreadEvents(var Message: TMessage); override;
procedure ExecuteInThread(AThread: TThreadClient); override;
end;
procedure TfrmPDoxClientDataOI.FormCreate(Sender: TObject);
begin
inherited;
dtmPDoxClientDataOI := TdtmPDoxClientDataOI.Create(self);
TheThread.Task := 1;
TheThread.Resume;
end;//FormCreate
procedure TfrmPDoxClientDataOI.ListenThreadEvents(var Message: TMessage);
begin
if (Message.WParam = 1) then begin
case Message.LParam of
//GUI tasks, using ClientDataset already compiled and not re-used
end;//case
end;//if
end;//ListenThreadEvents
procedure TfrmPDoxClientDataOI.ExecuteInThread(AThread: TThreadClient);
begin
while not AThread.Terminated and (AThread.Task <> 0) do begin
case AThread.Task of
1: begin
if dtmPDoxClientDataOI.GetData(...) then
if not AThread.Terminated then begin
PostMessage(Handle,WM_THREADCOMM,1,1);
AThread.Task := 2;
end //if
else
AThread.Task := 0;
end;//1
... etc...
end;//case
end;//while
end;//ExecuteInThread
Итак, когда конечный пользователь нажимает кнопку, создается новая форма и собственный модуль данных и поток; поток использует свой собственный модуль данных с помощью функции ExecuteInThread. Когда данные готовы, в форму отправляется сообщение PostMessage, которое обновляет диаграмму.
Как пишет Ливен, это зависит.
Если у вас есть компоненты базы данных в модуле данных, вы должны знать, являются ли они потокобезопасными, или сделать их потокобезопасными.
Некоторым компонентам базы данных требуется отдельный объект сеанса для каждого потока.
Чтобы использовать наш любимый ответ отрасли, когда спрашивают что-нибудь: это зависит.
Если у вас есть метод в вашем модуле данных, который полностью автономен (то есть может быть статическим методом), у вас не должно быть никаких проблем.
пример
TMyDataModule = class(TDataModule)
public
function AddOne(const Value: Integer): Integer;
end;
function TMyDataModule.AddOne(const Value: Integer): Integer;
begin
Result := Value + 1;
end;
Если, с другой стороны, метод использует какое-либо глобальное состояние, вы можете столкнуться с проблемами при вызове его из нескольких потоков.
пример
TMyDataModule = class(TDataModule)
private
FNumber: Integer
public
function AddOne(const Value: Integer): Integer;
end;
function TMyDataModule.AddOne(const Value: Integer): Integer;
begin
FNumber := Value
//***** A context switch here will mess up the result of (at least) one thread.
Result := FNumber + 1;
end;
Глобальное состояние должно интерпретироваться очень широко. TQuery, TTable, обновляющий графический интерфейс, использующий любую глобальную переменную,... является глобальным состоянием и не является поточно-ориентированным.
При работе с модулем данных в потоке возникает проблема: если вы завершаете свой поток в событии формы OnDestroy и ждете его (WaitFor) - у вас будет тупик. Блокировка набора основного потока пользовательского интерфейса
procedure TCustomForm.BeforeDestruction;
begin
GlobalNameSpace.BeginWrite;
и ваш поток будет бесконечно ждать в деструкторе модуля данных с тем же
destructor TDataModule.Destroy;
begin
if not (csDestroying in ComponentState) then GlobalNameSpace.BeginWrite;
Итак, если вы хотите дождаться своих потоков при закрытии MainForm, сделайте это в событии OnClose или в основном файле проекта.
Или вы можете уничтожить его в Synchronize
Краткий ответ: да
Длинный ответ: проблема с Windows заключается в том, что все действия с графическим интерфейсом должны выполняться в одном потоке. (Ну, приведенное выше утверждение может быть расширено, исправлено, дополнено и т. Д., Но для нашего обсуждения этого достаточно). Итак, если вы уверены, что в вашем методе TDataModule нет никакой "вещи с графическим интерфейсом" (будьте осторожны, это может быть даже ShowMessage
позвони) тогда иди вперед.
ОБНОВЛЕНИЕ: Конечно, есть методы для обновления вашего GUI из вторичного потока, но это подразумевает некоторую подготовку (передача сообщений, Synchronize
так далее.). Это не что-то очень сложное, просто вы не можете "вслепую" вызывать из другого потока метод, который меняет графический интерфейс.