【问题标题】:How to Thread-safe a ClientDataSet on a worker thread?如何在工作线程上对 ClientDataSet 进行线程安全?
【发布时间】:2017-09-16 01:47:44
【问题描述】:

我有一个非常慢的查询,总是让 Windows 将我的程序标记为无响应。我决定创建一个后台工作者来执行这个查询,而主线程显示一个 GIF。我做了一切,它的工作原理! =D

但是...当我关闭我的表单时,我得到了一个 EInvalidPointer 异常,只有当我使用工作线程时。

这是我的代码:

主线程调用工作线程

if not TThreadDB.ExecutarThreadDB(cdsSolicitacao,
                                   FConsultaSql,
                                   nil,
                                   tpHigher) then
 begin
   Exit;
 end;

在哪里: cdsSolicitacao 是我想在线程之间共享的 clientDataSet, FConsultaSql 字符串(我的查询)

我的线程单元

unit UThreadDB;

interface 

uses Classes, DBClient, DB, SysUtils, Variants, JvJCLUtils;

type

  TParametros = class
  private
    FTotal: Integer;
    FCampo: array of string;
    FTipo: array of TFieldType;
    FValor: array of Variant;
  public
    constructor Create(ACampos: string; ATipos: array of TFieldType; AValores: array of Variant); reintroduce;
  end;

  TThreadDB = class(TThread)
  private
    FExecutou: Boolean;
    FClientDataSet: TClientDataSet;
    FConsultaSQL: string;
    FParametros: TParametros;
    procedure CarregarDados;
  protected
    procedure Execute; override;
  public
    constructor Create(ACreateSuspended: Boolean; AClientDataSet: TClientDataSet; AConsultaSQL: string = '';
      AParametros: TParametros = nil); reintroduce;

    class function ExecutarThreadDB(AClientDataSet: TClientDataSet; AConsultaSQL: string = '';
      AParametros: TParametros = nil; APriority: TThreadPriority = tpNormal): Boolean;

    class procedure ExecutarThreadDBParalela(AThreadDB: TThreadDB; AClientDataSet: TClientDataSet;
      AConsultaSQL: string = ''; AParametros: TParametros = nil; APriority: TThreadPriority = tpNormal);
  end;

implementation

uses
   BIBLIO;

{ TThreadDB }

class function TThreadDB.ExecutarThreadDB(AClientDataSet: TClientDataSet; AConsultaSQL: string = '';
   AParametros: TParametros = nil; APriority: TThreadPriority = tpNormal): Boolean;
var
   lThreadDB: TThreadDB;
begin
   lThreadDB := TThreadDB.Create(True, AClientDataSet, AConsultaSQL, AParametros);

   try

      //lThreadDB.FreeOnTerminate := True;
      lThreadDB.Priority := APriority;
      lThreadDB.Resume;

      lThreadDB.WaitFor;

      Result := lThreadDB.FExecutou;

   finally
      lThreadDB.Terminate;

      //lThreadDB := nil;
      FreeAndNil(lThreadDB);
   end;
end;

class procedure TThreadDB.ExecutarThreadDBParalela(AThreadDB: TThreadDB; AClientDataSet: TClientDataSet;
   AConsultaSQL: string = ''; AParametros: TParametros = nil; APriority: TThreadPriority = tpNormal);
begin
   AThreadDB := TThreadDB.Create(True, AClientDataSet, AConsultaSQL, AParametros);

   AThreadDB.FreeOnTerminate := True;
   AThreadDB.Priority := APriority;
   AThreadDB.Resume;
end;

procedure TThreadDB.CarregarDados;
var
   lIndex: Integer;
begin
   FClientDataSet.Close;

   try

      if (FConsultaSQL <> '') then
      begin
         FClientDataSet.CommandText := FConsultaSQL;
      end;

      if (FParametros <> nil) then
      begin
         for lIndex := 0 to (FParametros.FTotal - 1) do
         begin
            case FParametros.FTipo[lIndex] of
               ftInteger : FClientDataSet.Params.ParamByName(FParametros.FCampo[lindex]).AsInteger := FParametros.FValor[lIndex];
               ftString  : FClientDataSet.Params.ParamByName(FParametros.FCampo[lindex]).AsString  := FParametros.FValor[lIndex];
               ftDate    : FClientDataSet.Params.ParamByName(FParametros.FCampo[lindex]).AsDate    := FParametros.FValor[lIndex];
            end;
         end;
      end;

      FClientDataSet.Open;

      FExecutou := True;

   except
      on E: Exception do
      begin
        Erro('Não foi possível carregar os dados solicitados!' + #13 +
             'Classe do erro: ' + E.ClassName + #13 +
             'Mensagem: ' + E.Message);
      end;
   end;

   if (FParametros <> nil) then
   begin
      FreeAndNil(FParametros);
   end;
end;

constructor TThreadDB.Create(ACreateSuspended: Boolean; AClientDataSet: TClientDataSet; AConsultaSQL: string = '';
   AParametros: TParametros = nil);
begin
  inherited Create(ACreateSuspended);

   FClientDataSet := AClientDataSet;

   FConsultaSQL := AConsultaSQL;

   FParametros := AParametros;

   FExecutou := False;
end;

procedure TThreadDB.Execute;
begin
   CarregarDados;
end;

{ TParametros }

constructor TParametros.Create(ACampos: string; ATipos: array of TFieldType; AValores: array of Variant);
var
   lIndex: Integer;
begin
  inherited Create;

   FTotal := ContaCaracteres(ACampos, ';') + 1;

   SetLength(FCampo, FTotal);
   SetLength(FTipo, FTotal);
   SetLength(FValor, FTotal);

   for lIndex := 0 to FTotal - 1 do
   begin
      FCampo[lIndex] := ExtractDelimited(lIndex + 1, ACampos , [';']);
   end;

   for lIndex := 0 to FTotal - 1 do
   begin
      FTipo[lIndex] := ATipos[lIndex];
   end;

   for lIndex := 0 to FTotal - 1 do
   begin
      FValor[lIndex] := AValores[lIndex];
   end;
end;

end.

关于我缺少什么的任何想法?

【问题讨论】:

  • 你的大部分代码(这都是脱离上下文的)。并在调试器中花费一些时间来弄清楚它到底发生在哪里。
  • 我编辑了一些可能使我的问题更清楚的信息
  • 这不是你的实际代码,因为你自己没有调用TThread.Execute;它在线程启动时自动调用。如果您需要代码方面的帮助,请发布您的代码,而不是您为帖子发明的东西。不要在不发布实际代码的情况下浪费人们的时间来尝试解决问题。见minimal reproducible example
  • 就像 Ken 所说的,您发布的代码与您所描述的并不完全相符。这如何让您的主线程为 gif 设置动画?来自主线程的调用正在等待您的 DbThread,没有任何并行。这应该阻塞主线程,不是吗?那么如何关闭表单并获取描述的 EInvalidPointerException 呢?
  • 至于在线程之间共享 ClientDataset,这可能会导致您的异常。 ExecutarThreadDBParalela 让线程使用对 ClientDataset 的引用进行工作,该引用可能在您的线程没有注意到的情况下被破坏。而且您的设计不会让运行 DBThreads 的主线程关闭。

标签: multithreading delphi


【解决方案1】:

您在问题中没有提到这一点,但我猜您描述的问题可能是由修改数据集对象引起的,该对象同时被主线程消耗(例如显示在网格中)。换句话说,您将传递给工作线程数据集,该数据集链接到主线程中的某些控件。或者,以另一种方式描述,您的 cdsSolicitacao 数据集对象通过数据源对象链接到主窗体上的某些控件。

请注意,主线程永远不能处理正在被工作线程修改的对象

甚至模态动画也不会阻止主线程使用刚刚修改的数据集。例如,可以请求重新绘制 DB 网格,这需要访问其底层数据集,同时由工作线程修改。

如果这是您的情况,并且您不想创建一个新的数据集实例,然后在工作线程完成时将其替换为使用的数据集实例, 在传递给工作线程之前,您需要(理想情况下)断开该数据集对象与每个链接控件的连接,并在完成时重新连接。

【讨论】:

  • 这正是我的情况,伙计!因此,为了释放我的 UI 线程,我必须在我的工作线程内创建一个新数据集并替换我当前共享的数据集或断开数据集。非常感谢。我试试看!
  • 不客气!是的,这是解决问题的正确方法(您可以通过主线程创建一个新实例并将其传递给工作线程,或者由工作线程生成一个实例,或者断开现有实例与所有链接控件的连接)。附言我不是男人:D
  • 对不起,@Victoria!我非常绝望地寻找解决这个问题的方法,以至于我什至没有读到你的名字。再次感谢! =D
猜你喜欢
  • 2011-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-15
相关资源
最近更新 更多