Вызовите метод TDataModule в TThread.Execute. - PullRequest
3 голосов
/ 01 марта 2010

В общем, возможно ли в процедуре TThread.Execute вызвать метод TDataModule, в котором не задействована визуальная активность?

Спасибо всем, Массимо.

Ответы [ 5 ]

2 голосов
/ 01 марта 2010

Самый простой способ - использовать 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 или какой-либо другой потокобезопасный эквивалентный способ сигнализации основного потока о том, что вам нужно что-то сделать.
    • Подумайте о том, что происходит, когда вы выключаетесь. Возможно, вы могли бы проверить, существует ли вообще ваш модуль данных, поскольку он мог исчезнуть, прежде чем вызывать его методы.
0 голосов
/ 02 марта 2010

Да, мой вопрос очень расплывчатый.

Моя программа представляет собой приложение с графической статистикой, оно должно отображать диаграмму Ганта с помощью 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 отправляется в форму, которая обновляет график.

0 голосов
/ 02 марта 2010

Как пишет Ливен, это зависит.

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

0 голосов
/ 01 марта 2010

Чтобы ответить на наш любимый ответ в любой отрасли: Это зависит.

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

Пример

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

0 голосов
/ 01 марта 2010

Краткий ответ: да

Длинный ответ: проблема с Windows заключается в том, что все действия с графическим интерфейсом должны выполняться в одном потоке. (Ну, приведенное выше утверждение может быть расширено, исправлено, дополнено и т. Д., Но для нашего обсуждения этого достаточно). Итак, если вы уверены, что в вашем методе TDataModule нет никаких «графических элементов» (будьте осторожны, это может быть даже вызов ShowMessage), тогда продолжайте.

ОБНОВЛЕНИЕ: Конечно, существуют способы обновления вашего GUI из вторичного потока, но это подразумевает некоторую подготовку (передача сообщений, Synchronize и т. Д.). Разве это не что-то очень сложное, просто вы не можете «вслепую» вызвать из другого потока метод, который меняет графический интерфейс.

...