Raffaele Rossi
Raffaele Rossi

Reputation: 3127

Delphi TTask get data from main thread

From what I have understood reading Nick Hodges, this code should be fine:

TTask.Run(
  procedure
  var
    resp, tmp: string;
    req: boolean;
    bwriter: TBinaryWriter;
    myfile: TFileStream;
  begin
    //tell the user to wait
    TThread.Queue(TThread.CurrentThread,
      procedure
      begin
        LoginButton.Text := 'Please wait...';
      end
    );

    //some checks
    try
      resp := GetURL('... here I get a result from the server...');
      if (resp = fOKstatus) then
      begin
        req := true;

        myfile := TFileStream.Create(TPath.Combine(TPath.GetHomePath, 'docs.mkb'), fmCreate);
        try
          bwriter := TBinaryWriter.Create(myfile, TEncoding.Unicode, false);
          try
            bwriter.Write(UsernameEdit.Text);
            bwriter.Write(AppIDEdit.Text);
            bwriter.Close;
          finally
            bwriter.Free;
          end;
        finally
          myfile.Free;
        end;
      end
      else
      begin
        req := false;
      end;
    except
      req := false;
    end;

    //final
    TThread.Queue(TThread.CurrentThread,
      procedure
      begin
        if (req = true) then
        begin
          LoginButton.Text := 'Success!';
          ShowMessage('Close the app to complete the registration.');
        end
        else
        begin
          LoginButton.Text := 'Login failed.';
        end;
      end
    );

  end
);

This runs in a separated thread, and it is linked to the main thread with the calls to Queue(). In fact, at the beginning I am updating the Text of a Button using this method.


QUESTION. Look at these 2 lines:

bwriter.Write(UsernameEdit.Text);
bwriter.Write(AppIDEdit.Text);

I need to retrieve the username and AppID (which is a random code) from two Edit controls that are in the main thread UI. Is this correct?

I guess that I should call Queue(), but so far the program is working well.

Can I take the values in this way safely? I am not updating anything, and I just need to grab the data, but I am not sure if mixing contents from 2 different tasks can be dangerous/bad practice.

Upvotes: 2

Views: 2474

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595652

The 2 lines of code you are concerned about are NOT thread-safe. You must synchronize with the main thread for all UI access, both reading and writing. TThread.Queue() is asynchronous, so it is not suitable for the purpose of retrieving values from the UI. Use TThread.Synchronize() instead, which is synchronous:

TTask.Run(
  procedure
  var
    resp, tmp, username, appid: string;
    req: boolean;
    bwriter: TBinaryWriter;
    myfile: TFileStream;
  begin
    //tell the user to wait
    TThread.Queue(nil,
      procedure
      begin
        LoginButton.Text := 'Please wait...';
      end
    );

    //some checks
    try
      resp := GetURL('... here I get a result from the server...');
      if resp = fOKstatus then
      begin
        req := true;

        TThread.Synchronize(nil,
          procedure
          begin
            username := UsernameEdit.Text;
            appid := AppIDEdit.Text;
          end
        );

        myfile := TFileStream.Create(TPath.Combine(TPath.GetHomePath, 'docs.mkb'), fmCreate);
        try
          bwriter := TBinaryWriter.Create(myfile, TEncoding.Unicode, false);
          try
            bwriter.Write(username);
            bwriter.Write(appid);
            bwriter.Close;
          finally
            bwriter.Free;
          end;
        finally
          myfile.Free;
        end;
      end
      else
      begin
        req := false;
      end;
    except
      req := false;
    end;

    //final
    TThread.Queue(nil,
      procedure
      begin
        if req then
        begin
          LoginButton.Text := 'Success!';
          ShowMessage('Close the app to complete the registration.');
        end
        else
        begin
          LoginButton.Text := 'Login failed.';
        end;
      end
    );
  end
);

Alternatively, assuming the main UI thread is the one starting the TTask, you can read the 2 values before starting the TTask and let the anonymous procedure capture them:

var
  username, appid: string;
begin
  username := UsernameEdit.Text;
  appid := AppIDEdit.Text;

  TTask.Run(
    procedure
    var
      resp, tmp: string;
      req: boolean;
      bwriter: TBinaryWriter;
      myfile: TFileStream;
    begin
      //tell the user to wait
      TThread.Queue(nil,
        procedure
        begin
          LoginButton.Text := 'Please wait...';
        end
      );

      //some checks
      try
        resp := GetURL('... here I get a result from the server...');
        if resp = fOKstatus then
        begin
          req := true;

          myfile := TFileStream.Create(TPath.Combine(TPath.GetHomePath, 'docs.mkb'), fmCreate);
          try
            bwriter := TBinaryWriter.Create(myfile, TEncoding.Unicode, false);
            try
              bwriter.Write(username);
              bwriter.Write(appid);
              bwriter.Close;
            finally
              bwriter.Free;
            end;
          finally
            myfile.Free;
          end;
        end
        else
        begin
          req := false;
        end;
      except
        req := false;
      end;

      //final
      TThread.Queue(nil,
        procedure
        begin
          if req then
          begin
            LoginButton.Text := 'Success!';
            ShowMessage('Close the app to complete the registration.');
          end
          else
          begin
            LoginButton.Text := 'Login failed.';
          end;
        end
      );
    end
  );
end;

Upvotes: 5

Related Questions