Вызовите метод 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 так далее.). Это не что-то очень сложное, просто вы не можете "вслепую" вызывать из другого потока метод, который меняет графический интерфейс.

Другие вопросы по тегам