Jump to content
ChrisChuah

Delphi 11.1 with TidThreadComponent and TThread.Synchronize

Recommended Posts

Hi

 

I have a component like this

 

TMyComponentEvent = procedure (Sender : TObject ; aData : string) of object;

TMyComponent = class(TComponent)

private

  FClient : TidTCPClient;

  FProcessList : TmyStringList;

  FReadThreadComponent : TidThreadComponent;

  FProcessThreadComponent : TidThreadComponent;

 

  FOnMyEvent : TMyComponentEvent;

  procedure DoMyEvent(aData : string); dynamic;

...

...

  procedure ReadThreadComponentRun(Sender : TidThreadComponent)

  procedure ProcessThreadComponentRun(Sender : TidThreadComponent)

..

..

published

  property OnMyEvent : TMyComponentEvent read FOnMyEvent write FOnMyEvent;

end;

 

procedure register;

begin

  RegisterComponents('MyComponents', [TMyComponent]);

end;

 

// cc : the UI screen will have this component and will have a set of code

// cc : to implement on this event

procedure TMyComponent.DoMyEvent(aData : string);

begin

  if assigned(FOnMyEvent) then

  FOnMyEvent(self, aData);

end;

 

// cc : assume that the TidTCPCLient is connected to the server

// cc : so the FReadThreadComponent will always get the data from TidTCPClient

procedure TMyComponent.ReadThreadComponentRun(Sender :TidThreadComponent)

var

  l_readBytes : TIdBytes;

  l_processdata : string;

begin

  FClient.IOhandler.ReadBytes(l_ReadBytes, -1, false);

  if length(l_readBytes) = 0 then exit;

  {There are some headers to remove from the l_readBytes 

  l_processData := ProcessidBytes(l_readBytes);

  FProcessList.add(l_processData);

end;

 

// cc : this is another idThreadComponent that will take the first item from the myStringList

// cc : and process it

procedure TMyComponent.ProcessThreadComponentRun(Sender : TidThreadComponent);

var

  l_data : string;

begin

   // cc : take the first item from the MyStringList

  l_data := FProcessList.getFirstData;

  if (l_data[1] = '1') then

    DoMyEvent(l_data);

end;

 

In the main form

 

TMainForm.MyComponentMyEvent(Sender : TObject; aData : string)

begin

  TThread.Synchronize (nil, procedure

  begin

    // update the FMX Form UI button, grid etc.

  end);

end;

 

Now, the question is, if i DO NOT use the TThread.Synchronize, there will be problem updating the UI grid etc.

What is the difference if i use the TThread. Synchronize and TThread.Queue

 

Somehow, if i use the Synchronize function, it is slower in updating the UI than TThread.Queue.

How can i make the UI updating more responsive?

Am i doing it correctly or can anyone advise?

 

Also there is some hanging issue whereby the function using the TThread.Queue can be called by Clipboard event. Maybe i will need to capture that event screen again

 

Share this post


Link to post
13 hours ago, ChrisChuah said:

Now, the question is, if i DO NOT use the TThread.Synchronize, there will be problem updating the UI grid etc.

Correct, because your event is being fired in a worker thread, so any access to UI controls must be synchronized with the main UI thread.

13 hours ago, ChrisChuah said:

What is the difference if i use the TThread. Synchronize and TThread.Queue

TThread.Synchronize() runs synchronously.  It will not return to the caller until the synched method has been called and exited.  That means your worker thread will be blocked waiting on the main UI thread to call the method.

 

TThread.Queue() runs asynchronously.  It will return to the caller immediately, and the synched method will run in the background at some unspecified time when the main UI thread gets around to calling it.  That means your worker thread will not be blocked waiting on the main UI thread, it will be free to do other things while the synched method waits to be called.

13 hours ago, ChrisChuah said:

Somehow, if i use the Synchronize function, it is slower in updating the UI than TThread.Queue.

Yes, because your worker thread can't do anything while TThread.Synchronize() is blocked waiting.  You don't have that issue with TThread.Queue().

13 hours ago, ChrisChuah said:

Also there is some hanging issue whereby the function using the TThread.Queue can be called by Clipboard event.

What do you mean? Please elaborate.

Share this post


Link to post

Hi remy

Ignore my last statement on the TThread.Queue can be called by Clipboard event. I am still trying to figure out why my application UI can hang or irresponsive when there is a large amount of data is received from the TidTCPClient. The TidThreadComponent will be busy processing and send out events to the MyEvent function as shown below 

 

TMainForm.MyComponentMyEvent(Sender : TObject; aData : string)

begin

  TThread.Synchronize (nil, procedure

  begin

    // update the FMX Form UI button, grid etc.

  end);

end;

 

My app is then reported by windows task manager that it is not responding.

How can i check where  the problem lies? Is it at the TThread.Synchronized  in the MyComponentMyEvent? I cannot click on any part of the application UI anymore.

This is an FMX application running in Windows 32 bit. Would I not face any of this hanging issue if i change to a VCL application instead of FMX?

 

Please advise

regards

Chris

 

Share this post


Link to post

I really REALLY dislike Synchronize.  It is a pitfall of pitfalls.


IMO, a better approach is:

  1. In your thread, send a signal when there is something that needs to be refreshed. That signal can be a queue, and you may want to include info about the type of content change if the UI should do a selective refresh
  2. Decide how often the UI should refresh and make a timer in the UI that checks the queue if something should be refreshed

You still need to protect access to elements shared by the UI thread and the background threads - but at least you will have FULL control over what the UI draws and when.

  • Like 1

Share this post


Link to post

I agree with you. It is really a headache when dealing with Thread and synchronisation.

In Indy TCP component, i need to use the Thread component to read the TCP socket constantly for data.

When there is data, need to append to a buffer or memstream as network data does not come in nice chunk of data with start and end.

Then we need to process that memstream for the appropriate data and remove the part of the data process from the memstream and add to another queue or List.

Then we need to create another thread to process this List to send out events. Need to create the thread so that it will hog up the first thread that read data from the TCP component.

In this processing thread, need to fire event based on the message. 

In this event, if there is a need to update the Main form UI, we need to use the TThread.Synchronize or TThread.Queue. If I were to update the data in memory, then i would not need to use TThread isnt it? <== Am i on the correct path?

 

So, if i update the data in memory and i use the timer to refresh the Main form UI, Do i still need to use TThread.Synchronize or TThread.Queue?

But using System.TTimer to refresh the data would have a lower priority in refreshing as if there are lots of data coming in on the TCPClient, the updates would use up the CPU and the timer will be set back to later time when the CPU is less busy isnt it?

 

Please advise

 

 

Share this post


Link to post
2 minutes ago, ChrisChuah said:

So, if i update the data in memory and i use the timer to refresh the Main form UI, Do i still need to use TThread.Synchronize or TThread.Queue?

Not if you use a thread safe queue and the access to the data that needs to be updated are protected with f.x. a critical section, or you only access data that you know will not be touched by other threads.

 

2 minutes ago, ChrisChuah said:

But using System.TTimer to refresh the data would have a lower priority in refreshing as if there are lots of data coming in on the TCPClient, the updates would use up the CPU and the timer will be set back to later time when the CPU is less busy isnt it?

Lets say you have a 500ms timer.  If you have activities on the TCPClient that receive data every 50 ms - the UI would still only refresh only every 500 ms

The only time you would get an issue with performance in the UI, is if the render takes longer than the timer - but you would not lose any data - and assuming the render contains all the most recent data - it would all be displayed, albeit at your fixed timer frequency.

 

In most cases, throttling the UI updates to refresh less often than the actual data refresh-rate will not be an issue.

If your TCPClient thread consumes all your CPU - you have a very different problem.

 

The only way it would be a problem, is if you use UI elements as data storage, instead of having an underlying structure as you should.

Divide and conquer. Separate your UI from your business logic.

  • Like 1

Share this post


Link to post
35 minutes ago, Lars Fosdal said:

The only time you would get an issue with performance in the UI, is if the render takes longer than the timer

This can also be easily solved if you either disable timer while you are updating UI and enable it back when you are finished. Alternative solution is adding some boolean flag and simply skipping UI update if previous update is still running.

  • Like 1

Share this post


Link to post
9 hours ago, ChrisChuah said:

I am still trying to figure out why my application UI can hang or irresponsive when there is a large amount of data is received from the TidTCPClient.

If you are doing a lot of syncing with the main UI thread, you are likely overwhelming the main thread with too many requests, leaving it with less opportunity to handle UI messages.  Every time you call TThread.Synchronize() or TThread.Queue(), it puts the synced method into a list and then posts a message to the main thread to let the RTL know that the list is pending.  Once the main thread processes that message, it runs through the entire list, calling each method in order, and doesn't process new messages again until after the list is empty.  If you are syncing often, you are going to fill the main thread's message queue with pending requests, and so the main thread is going to spend its time checking and rechecking that list instead of doing other things.

 

So, try to reduce how often you sync with the main thread.  For instance, batch up your synced data and then sync the whole batch after X updates are cached, or after N seconds have elapsed, etc.  Or, save off just your latest data and use a UI timer in the main thread to grab that data periodically.

9 hours ago, ChrisChuah said:

My app is then reported by windows task manager that it is not responding.

That means the main UI thread is doing too much work and is not processing UI messages often enough.  You need to relieve the pressure on the main thread.

9 hours ago, ChrisChuah said:

This is an FMX application running in Windows 32 bit. Would I not face any of this hanging issue if i change to a VCL application instead of FMX?

No, this would affect VCL in much the same way, as most of the functionality for TThread.Synchronize()/TThread.Queue() is common RTL code that is shared by both frameworks.

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
×