Popa Ovidiu-Razvan
Popa Ovidiu-Razvan

Reputation: 495

How to execute thread synchronize from a separate unit

I have the following problem/question.

I have a unit named "myGlobalFunctions.pas". Inside this unit I have implemented multiple procedures/functions that are used by several projects.

project 1 use this unit

project 3 use this unit

project 6 use this unit etc

inside "project 1" there is a thread that use functions inside the "global function" unit.

inside project 3 there is no thread but the functions are used.

so far this thread (project1) provide almost no update of the application interface and the update was made AFTER or BEFORE calling a function from "myGlobalFunctions.pas"

like "before start function1" ... the calling "after function1".

this way I can know what the program is doing.

However now I want to implement inside the "function1" update of the application interface (with synchronize).

I want to reflect in the application interface "processing step1 ... xx records". (there is a while loop there for a dataset).

using Synchronize for "project1" and with normal label1.caption = 'message'; application.process messages for any other project.

is it possible?

how can I do such a thing.

can be Thread Safe ?

tks a lot

Razvan here is some code to understand better

THREAD UNIT

procedure TThreadSyncronizeProcess.SignalStart;
begin
  frmMain.sbMain.Panels[2].Text := 'Syncronizare in desfasurare...'; -- exist all the time
  if Assigned(frmSyncronize) then begin     -- check if exist this
    frmSyncronize.logMain.WriteFeedBackMessage('Pornire syncronizare...', '', EVENTLOG_INFORMATION_TYPE, True);
  end;
end;


procedure TThreadSyncronizeProcess.Execute;
var ..... declarations
begin
  Synchronize(SignalStart); -- this is normal call within thread update interface
  try
    try
      workSession        := TIB_Session.Create(nil);
      workDatabase       := TIB_Database.Create(workSession);
        ... creating more components and setup them ...

      uSyncronizareFunctions.SetupDatabase(workDatabase, workSession, transactionWrite, transactionRead);
      uSyncronizareFunctions.SetupDataSnapConnection(workConnectionRead, providerRead);
      if Assigned(frmSyncronize) then begin
        uSyncronizareFunctions.SetupFeedBack(frmSyncronize.logMain);
      end;

      try
          Synchronize(SignalMessage);
          // this next function is from the "global unit"
          isAllOk := uSyncronizareFunctions.ImportOperatoriAutorizati(workImage, workLabelProgress, True);
          isAllOk := isAllOk and uSyncronizareFunctions.ImportJudete;
          isAllOk := isAllOk and uSyncronizareFunctions.ImportLocalitati;
          isAllOk := isAllOk and uSyncronizareFunctions.ImportUM;
          isAllOk := isAllOk and uSyncronizareFunctions.ImportFurnizori;
          isAllOk := isAllOk and uSyncronizareFunctions.ImportClasificari;
      except
        on e : Exception do begin
          raise Exception.Create(dmMain.GetDataSnapExceptionMessage(e.Message));
        end;
      end;
    except
      on e : Exception do begin
        baseMessage := e.Message;
        Synchronize(SignalMessage);
      end;
    end;
  finally
    workDatabase.ForceDisconnect;
    FreeAndNil(transactionRead);
        ... etc
  end;
  Synchronize(SignalFinish);
end;



global function unit
unit uSyncronizareFunctions;

function  ImportOperatoriAutorizati(imgDone : TImage; labelProgress : TLabel; isThread : Boolean) : Boolean;
var workQuery  : TIB_Query;
    serverData : TClientDataSet;
begin
  Result := True;
  try
    ... create all that we need

    serverData.Close;
    serverData.CommandText := 'SELECT * FROM OPERATORI_AUTORIZATI WHERE REC_VERSION > :ARECVERSION ORDER BY REC_VERSION, ID';
    serverData.Params.Clear;
    serverData.Params.CreateParam(ftInteger, 'ARECVERSION', ptInput);
    serverData.Params.ParamByName('ARECVERSION').AsInteger := lastVersion;
    serverData.Active := True;

        ...... I want here to signal start

    while not serverData.Eof do begin
      try
        globalInsert_Tran.StartTransaction;

        workQuery.Close;
        workQuery.ParamByName('AIDGLOBAL').AsString := serverData.FieldByName('IDGLOBAL').AsString;
        workQuery.Open;
        if workQuery.IsEmpty then begin
          workQuery.Insert;
          workQuery.FieldByName('IDGLOBAL').AsString   := serverData.FieldByName('IDGLOBAL').AsString;
        end else begin
          workQuery.Edit;
        end;
        workQuery.FieldByName('NUME').AsString           := serverData.FieldByName('NUME').AsString;
        workQuery.FieldByName('COD_AUTORIZARE').AsString := serverData.FieldByName('COD_AUTORIZARE').AsString;
        workQuery.FieldByName('OTHER_INFO').AsString     := serverData.FieldByName('OTHER_INFO').AsString;
        workQuery.FieldByName('DATASTERGERE').AsVariant  := GetValueDate(serverData.FieldByName('DATASTERGERE').AsDateTime);
        workQuery.FieldByName('REC_VERSION').AsInteger   := serverData.FieldByName('REC_VERSION').AsInteger;
        workQuery.Post;

        MarkRecordAsDirtyFalse(workQuery);
        globalInsert_Tran.Commit;

        ...... I want here to signal progress and to see in the application interface "processing record xx/100" or any other message


      except
        on e : Exception do begin
          Result := False;
          globalInsert_Tran.Rollback;
        end;
      end;

      serverData.Next;
    end;
  finally
    FreeAndNil(serverData);
    FreeAndNil(workQuery);
  end;
end;

Upvotes: 4

Views: 629

Answers (1)

J...
J...

Reputation: 31403

It looks like you would like your global function to execute a callback. You might try an approach like this:

unit MyGlobalMethods;    

interface
  uses
    System.SysUtils;
  type
    // define a method signature for your callback
    TSomeCallback = procedure(progress : integer) of object;

  // add a callback argument to your function (initializing to nil will make
  // the parameter optional and will not break your previous implementations)
  function GlobalFunction(arg1 : integer; 
                          AMethodCallback : TSomeCallback = nil) : boolean;

implementation

function GlobalFunction(arg1 : integer; 
                        AMethodCallback : TSomeCallback) : boolean;
var i : integer;
begin
  for i := 0 to arg1 do begin
    sleep(10);  // Do some work
    // report progress by executing the callback method
    // only do this if a method has been passed as argument
    if (i mod 100 = 0) and (Assigned(AMethodCallback)) then AMethodCallback(i);
  end;
  result := true;
end;

end.

Adding a method callback as an argument allows you to pass in any function you like to have the method execute. For example :

  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    procedure UpdateProgress(progress : integer);
  end;

  TSomeThread = class(TThread)
    private
      FProgressCallback : TSomeCallback;
      FProgress : integer;
      procedure SynchronizeCallback(progress : integer);
      procedure DoCallback;
    public
      procedure Execute; override;
      property OnFunctionProgress : TSomeCallback 
                               read FProgressCallback write FProgressCallback;
  end; 

implement as :

procedure TSomeThread.Execute;
begin
  GlobalFunction(1000, SynchronizeCallback);
end;

procedure TSomeThread.SynchronizeCallback(progress: Integer);
begin
  FProgress := progress;
  Synchronize(DoCallback);
end;

procedure TSomeThread.DoCallback;
begin
  if Assigned(FProgressCallback) then FProgressCallback(FProgress);
end;

You haven't told us what version of Delphi you are using. If you are using D2009 or newer you can bundle the above two calls into one using anonymous methods (and get rid of FProgress) :

procedure TSomeThread.SynchronizeCallback(progress: Integer);
begin
  Synchronize(procedure
              begin
                if Assigned(FProgressCallback) then FProgressCallback(progress);
              end;);
end;

Where in your form you would do :

procedure TForm1.UpdateProgress(progress: Integer);
begin
  label1.Caption := IntToStr(progress);
end;

procedure TForm1.Button1Click(Sender: TObject);
var someThread : TSomeThread;
begin
  someThread := TSomeThread.Create(true);
  someThread.FreeOnTerminate := true;
  someThread.OnFunctionProgress := UpdateProgress;
  someThread.Start;
end;

This nicely separates the responsibilities. The main form passes an update method to the thread (a method, in this case, to update a label). The thread is responsible for synchronizing the call and the global function, therefore, does not need to care whether or not the callback it is executing originates from the main thread or from any other thread. The thread knows it needs to synchronize the method so it should take that responsibility.

Upvotes: 5

Related Questions