如何在不冻结的情况下读取blob字段?

3
我想在客户端(通过网络)读取blobfield(使用blobstream),但在获取数据时应用程序会冻结。如何在不冻结的情况下读取blobfield,并且使用进度条显示百分比(我正在使用Delphi和Firebird)。
我正在使用uniquery组件。我从http://forums.devart.com/viewtopic.php?t=14629找到了以下代码,但它无法正常工作:
const
BlockSize= $F000;
var
Blob: TBlob;
Buffer: array of byte;
p: pointer;
pos, count: integer;

  UniQuery1.SQL.Text:= 'select * from TABLE1 where FIELD_ID = 1';
  UniQuery1.Open;

  blob:= uniquery1.GetBlob('DATA');
  SetLength(buffer, blob.Size);
  ProgressBar1.Position:= 0;
  Application.ProcessMessages;

 repeat
  count:= Blob.Read(pos, blocksize, p);
  ProgressBar1.Position:= Round(pos/Blob.Size * 100);
  pos:= pos + count;
  p:= pointer(integer(p) + count);
  Application.ProcessMessages;
 until count < blocksize;

PS:我已经设置了UniQuery的选项:

cacheblobs:= false;
streamedblobls:= true;
deferredblobread:= true;

在repeat-until循环的第一步中,Blob.Read方法读取了整个流,因此它无法正常工作。

1
将您的 Blob 查询放在具有自己连接的线程中? - whosrdaddy
1
你使用哪些组件来连接Firebird? - Harriv
1
我会创建一个带有进度报告功能的TBlobStream子类,并像@whosrdaddy所说,将您的DB内容放到单独的工作线程中。 - TLama
相关:https://dev59.com/mGzXa4cB1Zd3GeqPZfEV - Jens Mühlenhoff
1个回答

1

你应该使用线程,这里有一个使用 Delphi TThread 的例子:

type
  TMyForm = class(TForm)
  private
    FPosition: Integer;
    procedure ProgressUpdate;
    procedure Execute;
  end;

procedure TMyForm.ProgressUpdate;
begin
  ProgressBar1.Position := FPosition;
end;

procedure TMyForm.Execute;
begin
  FPosition:= 0;
  ProgressUpdate;
  Thread := TThread.CreateAnonymousThread(procedure
    begin
      repeat
        // Do some long running stuff (in chunks, so we can update the position)
        FPosition := CalculatePosition;
        // Important: Synchronize will run ProgressUpdate in the main thread!
        TThread.Synchronize(nil, ProgressUpdate); 
      until SomeCondition;
    end
  );
  Thread.Start;
end;

所以将此模式应用于您的代码后,我们得到:
type
  TMyForm = class(TForm)
  private
    FPosition: Integer;
    procedure ProgressUpdate;
    procedure Execute;
  end;

procedure TMyForm.ProgressUpdate;
begin
  ProgressBar1.Position := FPosition;
end;

procedure TMyForm.Execute;
var
  Blob: TBlob;
  Thread: TThread;
begin
  UniQuery1.SQL.Text := 'SELECT * FROM TABLE1 WHERE FIELD_ID = 1';
  UniQuery1.Open;
  Blob := UniQuery1.GetBlob('DATA');

  FPosition:= 0;
  ProgressUpdate;
  Thread := TThread.CreateAnonymousThread(
    procedure
    const
      BlockSize = $F000;
    var
      Buffer: array of Byte;
      P: Pointer;
      Pos, Count: Integer;
    begin
      SetLength(Buffer, Blob.Size);
      repeat
        Count := Blob.Read(Pos, BlockSize, P);
        FPosition := Round(Pos / Blob.Size * 100);
        Pos := Pos + Count;
        P := Pointer(Integer(P) + Count);
        // Important: Synchronize will run ProgressUpdate in the main thread!
        TThread.Synchronize(nil, ProgressUpdate); 
      until Count < BlockSize;
    end
  );
  Thread.Start;
end;

我删除了Application.ProcessMessage并将所有处理移动到线程中。

线程设置FPosition私有属性,并使用TThread.Synchronize在主线程中将进度条位置设置为FPosition

如果块大小不够大,则可能仍会阻止UI(由于过多的同步),因此请选择适当的块大小或添加一些更新延迟。

您必须确保在匿名线程运行时不要在主线程中使用UniQuery1对象的连接,或者也可以将连接和查询移动到线程中。

此外,这可能会出现重入问题,但它应该给您提供了如何使用线程进行后台处理的基本思路。

PS:如果查询需要一些时间,将查询放在线程中也是一个好主意。


抱歉,我不擅长线程, 在 TThread.Synchronize(ProgressUpdate); 行会出现错误: E2389 受保护成员 'TThread.Synchronize' 在此处无法访问。 - user3205651
1
TThread.Synchronize(nil, ProgressUpdate) 应该可以工作。请参阅 TThread.Synchronize。注意:当使用静态版本的 Synchronize 方法并将 nil/NULL 作为第一个参数时,它将在主线程中同步 AMethod 引用的方法。 - LU RD
这是正确的(已修复)。抱歉我没有测试过,但我只想给你正确的模式。 - Jens Mühlenhoff
你也应该注意我的警告,不要发送太多的进度更新。 - Jens Mühlenhoff
@user3205651 你应该花些时间学习线程,每个优秀的程序员都应该了解后台处理。 - Jens Mühlenhoff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接