2017-09-14 39 views
0

我有一个非常慢的查询,总是让Windows将我的程序标记为“未响应”。我决定创建一个后台工作来执行这个查询,而主线程显示一个GIF。我做了一切,它的工作原理! = D如何在工作线程上线程安全的ClientDataSet?

但是...当我关闭我的窗体时,我得到了一个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. 

我错过了什么想法?

+1

您的大部分代码(这是所有的情况下)。还有一段时间在调试器中弄清楚它到底发生了什么。 –

+0

我编辑了一些信息,可能会使我的问题更清晰 –

+2

这不是您的实际代码,因为您自己不会调用'TThread.Execute';它在线程启动时自动调用。如果你需要帮助你的代码,**发布你的代码**,而不是你为你的帖子发明的东西。不要浪费人们的时间试图解决问题,而不会发布实际的代码。见[mcve]。 –

回答

1

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

请注意,主线程无法处理正在由工作线程修改的对象。

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

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

+0

这就是我的情况,男人!所以为了释放我的UI线程,我必须在我的工作线程中创建一个新的数据集,并替换我正在共享或断开数据集的那个数据集。非常感谢。我会试试看! –

+0

不客气!是的,这是解决问题的正确方法(或者您通过主线程将新实例传递给工作线程,或者由工作线程产生一个新实例,或从现有链接控件中断开现有链接)创建新实例。附:我不是男人:D – Victoria

+1

对不起,@维多利亚!我非常渴望寻找解决这个问题的方法,我甚至没有读到你的名字。再次感谢! = d –