Jump to content
Fr0sT.Brutal

Conceptual - callbacks that are called from background thread

Recommended Posts

Hi all, I'm asking for advice. How do you deal with callbacks / events that are called in the context of bg thread?

For example, there's a class that, say, performs archiving of data async-ly. It has input buffer and an event with a chunk of data it processed (suppose it doesn't store it but throws away to a caller immediately). And it starts a bg thread to process data. In result, OnProcessedData is called from that bg thread.

type
  TOnDataProcessed = procedure (const Data: TBytes) of object;

  TСompressor = class
    constructor Create(const InputData: TBytes; OnDataProcessed: TOnDataProcessed);
  end;

{ TСompressor }

constructor TСompressor.Create(const InputData: TBytes; OnDataProcessed: TOnDataProcessed);
begin
  TThread.CreateAnonymousThread(
    procedure
    var ComprData: TBytes;
    begin
      repeat
        ComprData := Compress(InputData);
        OnDataProcessed(ComprData); // <-- callback from bg thread
      until Processed;
    end).Start;
end;

And if we create it with handler like usual...

Compr := TСompressor.Create(Input, СompressorDataProcessed);

procedure TForm1ompressorDataProcessed(const Data: TBytes);
begin
  Inc(Processed, Length(Data));  // <-- BANG! Access collision possible
  lblProcessed.Caption := IntToStr(Processed);
end;

So what's the best way to avoid such situations?

I underline - it is a library class. We can't be sure it will be always used in GUI env - so PostMessage is not a reliable option.

 

0) Comments/documentation - not an option, if some info could be missed, it will be missed

1) Naming convention like OnDataProcessedBgThread: TOnDataProcessedBgThread - simple but just a hint/reminder for a class user.

2) Synchronize is not much more useful because it could lead to deadlocks and lowers performance.

3) PostThreadMessage? Will require message processing loop which is not trivial for main application thread

4) Store records for each callback call in class' thread-safe queue and consume from it by a timer?

5) Ideas?

Share this post


Link to post

When you use this class you will know the context it is used in.

 

Do the synchronization (if needed) inside the callback method.

  • Like 1

Share this post


Link to post

Only who supplies the callback (your TForm1) knows if it's important to have it synced to the main thread or not. So it should take care of synchronizing/queueing it to the main thread, not your TCompressor.

  • Like 1

Share this post


Link to post

But wouldn't that require the "CallerThread" to support something like this? As far as I know, only the Delphi main thread has something like this, queueing/synchronizing something to a regular TThread has absolutely no effect.

Share this post


Link to post
1 hour ago, Der schöne Günther said:

But wouldn't that require the "CallerThread" to support something like this? As far as I know, only the Delphi main thread has something like this, queueing/synchronizing something to a regular TThread has absolutely no effect.

Yes, it is far from easy to switch processing to a specific secondary thread. Basically it is only possible savely if that thread has been written for this.

 

So i can only concur with your previous reply: the responsibility of handling the synchronizing should fall to the code providing the callback.

 

Share this post


Link to post

I prefer using thread safe queues  aka the mailslot principle.

Normally I let my worker threads have an workqueue and a reference to the mainthread responsequeue.

I post a task to the workqueue, getting a task ticket, and the background thread posts a response/result  package to the responsequeue, marked with that task ticket.

I can either poll the response queue with the ticket, or I can have the thread fire off a windows message to the main thread that basically says "check the queue for this task ticket"

Benefits: 100% async and isolated.  No concurrent access to data.  All updates can happen at the leisure of the main thread.

 

Share this post


Link to post

Thanks to all who answered, sorry for late response.

On 8/9/2019 at 11:56 AM, David Heffernan said:

Documentation is always an option. Don't rule it out. 

Docs are good but hardly ever being read from A to Z. Interface should be intuitive IMHO or at least give some hints.

On 8/9/2019 at 5:23 PM, Lars Fosdal said:

I prefer using thread safe queues  aka the mailslot principle.

Normally I let my worker threads have an workqueue and a reference to the mainthread responsequeue.

I post a task to the workqueue, getting a task ticket, and the background thread posts a response/result  package to the responsequeue, marked with that task ticket.

I can either poll the response queue with the ticket, or I can have the thread fire off a windows message to the main thread that basically says "check the queue for this task ticket"

Benefits: 100% async and isolated.  No concurrent access to data.  All updates can happen at the leisure of the main thread.

Very interesting regarding work queues. Have you got any shareable code to look at?

 

What about Synchronize/Queue? I always tried to avoid it but see Emba have done pretty much in this area. Is it safe?

Edited by Fr0sT.Brutal

Share this post


Link to post

Regarding documentation; my 5 cents: download trials and look at the docs or the "lessons" of a library called RTC (realthinclient.com). It is simply brilliant. But, yes... if you do not heed the documentation on multi-threading it goes BANG immediately. On the other hand, if you do follow the guidelines (and it is not that complicated) you can build stuff that simply rocks. On a lot of platforms and a lot of Delphi versions. I have not read all posts above, but of course you might have a team of code-monkeys to manage and it might prove that supplying them with a fool-proof API might make your shareholder happy. Those consideration are however, IMHO, out of scope here.

Share this post


Link to post

There are simply some rules of use for certain types of library that cannot be enforced effectively by the compiler, or even by runtime checks. For these rules you need documentation and developers that are prepared to read documentation. 

 

My personal view is that if a developer is not prepared to go to the trouble of reading the documentation, then any problems they have are their concern and not that of the library developer. 

Edited by David Heffernan

Share this post


Link to post
18 hours ago, Fr0sT.Brutal said:

Thanks to all who answered, sorry for late response.

Docs are good but hardly ever being read from A to Z. Interface should be intuitive IMHO or at least give some hints.

Very interesting regarding work queues. Have you got any shareable code to look at?

 

What about Synchronize/Queue? I always tried to avoid it but see Emba have done pretty much in this area. Is it safe?

Sorry, no code to share as it is embedded in a larger framework.
I do use the queues in Omni Thread Library from @Primož Gabrijelčič down in the core, though.

As for Synchronize: Just say no.

  • Thanks 1

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

×