Jump to content
softtouch

Waiting for something without blocking the UI

Recommended Posts

I need to call a function, with returns a string. Inside this function is a TCP request sent and the function wait for the reply, and then return that reply. Unfortunately, I need to wait in this function until the reply is available, then return the result to the calling code.

 

Example (not actual code):


This calls a function "GetTCPIPC".

ret:=GetTCPIPC('test only');


This is the function "GetTCPIPC":

function GetTCPIPC(msg:string):string;
begin
  if not clientclass.connected then
	begin
    result:='';
		exit;
  end;
  clientclass.msgvalid:=false;
  idTCPClient.IOHandler.WriteLn(msg);
  while not clientclass.msgvalid do
	begin
		sleep(0);
		Application.ProcessMessages
  end;
	result:=clientclass.msgfromserver;
end;

This function must somehow wait until the flag msgvalid is set, and then return something to the caller.

So with "Application.ProcessMessages", it works as it should, but I want to get rid of that ProcessMessages call, and still, this function need to return the string after it waits. How could I possible realize that?

Share this post


Link to post

You could use a thread, something like this:

 

type
  TMyThread = class(TThread)
  private
    FRet: string;
  protected
    procedure Execute; override;
  published
    property Ret: string read FRet;
  end;

  TForm5 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure HandleThreadTerminate(Sender: TObject);
  end;

var
  Form5: TForm5;

implementation

{$R *.dfm}

procedure TForm5.Button1Click(Sender: TObject);
var
  Thread: TMyThread;
begin
  Thread := TMyThread.Create(true);
  Thread.FreeOnTerminate := true;
  Thread.OnTerminate := HandleThreadTerminate;
  Thread.Start;
end;

procedure TForm5.HandleThreadTerminate(Sender: TObject);
begin
  caption := TMyThread(Sender).Ret;
end;

{ TMyThread }

procedure TMyThread.Execute;
begin
  if not clientclass.connected then
  begin
    FRet :='';
    exit;
  end;
  clientclass.msgvalid:=false;
  idTCPClient.IOHandler.WriteLn(msg);

  while (not Terminated) and (not clientclass.msgvalid) do
  begin
    FRet:=clientclass.msgfromserver;
    TThread.Sleep(0);
  end;
end;

 

Edited by ioan

Share this post


Link to post
38 minutes ago, softtouch said:

This is the function "GetTCPIPC":

...

This function must somehow wait until the flag msgvalid is set, and then return something to the caller.

So with "Application.ProcessMessages", it works as it should, but I want to get rid of that ProcessMessages call, and still, this function need to return the string after it waits. How could I possible realize that?

It seems like you are receiving the server's response asynchronously.  In which case, I would suggest not waiting for the response at all.  Send the request, and then move on.  Let the asynchronous handler notify your code whenever the response actually arrives.  You can always disable the UI in the meantime, if needed.

 

Otherwise, if you must make the function act synchronously, even though the response is asynchronous, then at least consider waiting on a TEvent instead of a boolean, eg:

function GetTCPIPC(const msg: string): string;
begin
  if not clientclass.Connected then
  begin
    Result := '';
    Exit;
  end;
  clientclass.MsgEvent.Reset;
  idTCPClient.IOHandler.WriteLn(msg);
  while clientclass.MsgEvent.WaitFor(10) <> wrSignaled do
    Application.ProcessMessages;
  Result := clientclass.msgfromserver;
end;

Otherwise, consider changing the function to read the response directly in the function itself, not asynchronously from elsewhere.  That way, you can place a TIdAntiFreeze component on your Form to keep the UI responsive while the TCP socket is blocking the UI thread, eg:

function GetTCPIPC(const msg: string): string;
begin
  if not clientclass.Connected then
  begin
    Result := '';
    Exit;
  end;
  IdTCPClient.IOHandler.WriteLn(msg);
  //read response here
  Result := msgfromserver;
end;

Though, you really should not be doing socket I/O in the UI thread at all.  Consider a threaded approach, similar to what ioan showed earlier.

 

  • Like 1

Share this post


Link to post

Thank you ioan and Remy!

The problem is, the main code should not continue until the result is received, thats crucial. Its has to behave just like a call to normal function which return a result. It will also be called from many units.

Share this post


Link to post

You could use a non-blocking asynchronous TCP component such as ICS (TWSocket and other for high level protocols).

With ICS, methods like Connect, Send, Receive and other are not blocking, they are merely a request which are almost instantaneous. Later when connection is established, data received or sent, you have an event. The UI is never blocked, even with hundreds of active connections. No need to use thread of wait loop (Of course you may use both if you like complexity).

You can install ICS from the IDE using GetIT, or download a zip file, or use the SVN repository. Have a look at http://wiki.overbyte.eu/wiki/index.php/ICS_Download.

Dephi-Praxis has an ICS dedicated support forum : https://en.delphipraxis.net/forum/37-ics-internet-component-suite/

  • Like 1

Share this post


Link to post
1 hour ago, softtouch said:

The problem is, the main code should not continue until the result is received, thats crucial. Its has to behave just like a call to normal function which return a result. It will also be called from many units.

You either block or not block. No 3rd option until async/await is added to Delphi. So you can implement your call as fully sync and place it to a thread when keeping interaction is needed or make it async so that you start it and go further and callback will be called when the request is done.

Share this post


Link to post
1 hour ago, softtouch said:

The problem is, the main code should not continue until the result is received, thats crucial.

That exactly describes a blocking behavior. As long as the specification says blocking and you want non-blocking, you better change the specification first before doing anything with the code.

  • Like 2

Share this post


Link to post
1 hour ago, Uwe Raabe said:

That exactly describes a blocking behavior. As long as the specification says blocking and you want non-blocking, you better change the specification first before doing anything with the code.

The problem here is, that in Delphi "blocking" means that the UI freezes, unless he calls Application.ProcessMessages, which has its own drawbacks (e.g. other events can be fired, some of which may not be desired).

  • Like 1

Share this post


Link to post
11 hours ago, softtouch said:

The problem is, the main code should not continue until the result is received, thats crucial. Its has to behave just like a call to normal function which return a result. It will also be called from many units.

I've already said my piece about that.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×