How can I read blobfield without freezing?

981 views Asked by At

I want to read blobfield (with blobstream) from client side (over network) but application freezes while fetching data. How can I read blobfield without freezing and showing percentage with a progressbar. (I'm using Delphi and Firebird)

i'm using uniquery component. i've found this code from: http://forums.devart.com/viewtopic.php?t=14629

but it doesn't work properly:

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: i've set uniquery's options:

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

in the first step of repeat-until loop, Blob.Read method reads all of stream, so it doesnt work properly.

1

There are 1 answers

5
Jens Mühlenhoff On

You should use a thread, here is an example with 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;

So after applying this pattern to your code we get:

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;

I removed the Application.ProcessMessage and moved all processing to the thread.

The Thread is setting the FPosition private attribute and uses TThread.Synchronize to set the ProgressBar position to FPosition in the main thread.

If your block size is not big enough this might still block the UI (due to excessive synchronization), so choose an appropriate block size or add some update delay.

You have to make sure that the connection of the UniQuery1 object is not used in the main thread while the anonymous thread is running or move the connection and query to the thread as well.

Also this can have reentrance problems, but it should give you a basic idea of how to use a thread for background processing.

PS: It might also be a good idea to run the query in the thread, especially if it can take some time.