[Lazarus] Enqueuing a callback from a thread via other class - or am I overcomplicating this ?

classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

[Lazarus] Enqueuing a callback from a thread via other class - or am I overcomplicating this ?

Free Pascal - Lazarus mailing list
Hi,

I have a TThread, that processes a T(Thread)List of TSomeClass objects continuously.

I want to have a callback in the TSomeClass, to which another class can put their procedure to call into,
but the callback needs to be 'thread safe' (because it can be accessed (read) by GUI related elements).

I tried like this:

TSomeClass = class

private
... // other variables/fields
public
  class var ProcessedCallback : TThreadMethod;
  var
   ... //other variavbles

  procedure Processed;
end;

procedure TSomeClass.Processed;
begin
  if Assigned(ProcessedCallback) then
    ProcessedCallback();
 
end;


and in the TMyThread I am trying to do

var SomeClassList : TThreadList; // is declared private to the thread - only the thread manages the lifetime of the list members

procedure TMyThread.Execute;
var
    SomeClassLockedList: TList;
begin
  i := 0;
  while not Terminated do
    begin
      if SomeClassList.Count = 0 then
      begin
        sleep(10);  
        continue;
      end;
      SomeClassLockedList := SomeClassList.LockedList; // there are try/excepts around all here, but did not want to muddle the picture
      Queue(TSomeClass(SomeClassLockedList.Items[i]).Processed);
      // but above here I'm getting an error : got 'untyped', expected 'procedure variable type of object;Register'.
      SomeClassList.UnlockList;
      i := i+1;
    end;
 
end;

// the above sort-of gives the gist of what I'm trying to achieve

What am I doing wrong ?
Does the procedure to be enqueued have to be declared in the TThread header ?

Hope some of it makes (any) sense ...

Thanks in advance,

-L.

--
_______________________________________________
Lazarus mailing list
[hidden email]
http://lists.lazarus-ide.org/listinfo/lazarus
Reply | Threaded
Open this post in threaded view
|

Re: [Lazarus] Enqueuing a callback from a thread via other class - or am I overcomplicating this ?

Free Pascal - Lazarus mailing list
On Wed, 14 Jun 2017 11:12:11 +0100
Lukasz Sokol via Lazarus <[hidden email]> wrote:

>[...]
>       SomeClassLockedList := SomeClassList.LockedList; // there are try/excepts around all here, but did not want to muddle the picture
>       Queue(TSomeClass(SomeClassLockedList.Items[i]).Processed);

Queue(@TSomeClass(SomeClassLockedList.Items[i]).Processed);
      ^

Mattias
--
_______________________________________________
Lazarus mailing list
[hidden email]
http://lists.lazarus-ide.org/listinfo/lazarus
Reply | Threaded
Open this post in threaded view
|

Re: [Lazarus] Enqueuing a callback from a thread via other class - or am I overcomplicating this ?

Free Pascal - Lazarus mailing list
On 14/06/17 11:33, Mattias Gaertner via Lazarus wrote:

> On Wed, 14 Jun 2017 11:12:11 +0100
> Lukasz Sokol via Lazarus <[hidden email]> wrote:
>
>> [...]
>>       SomeClassLockedList := SomeClassList.LockedList; // there are try/excepts around all here, but did not want to muddle the picture
>>       Queue(TSomeClass(SomeClassLockedList.Items[i]).Processed);
>
> Queue(@TSomeClass(SomeClassLockedList.Items[i]).Processed);
>       ^
>
> Mattias
>

Thanks! looks so easy in hindsight ;) same the callbacks need to be assigned with the @ in front :)

-L.

--
_______________________________________________
Lazarus mailing list
[hidden email]
http://lists.lazarus-ide.org/listinfo/lazarus
Reply | Threaded
Open this post in threaded view
|

Re: [Lazarus] Enqueuing a callback from a thread via other class - or am I overcomplicating this ?

Free Pascal - Lazarus mailing list
Hindsight, schmindsight, but here is another question:

Where does the call queued from a thread, return to ?

As in, the thread calls Queue() on a method of the
object we're interested in; and that in turn calls
a method / procedure more like / from the 'main form' unit.

The object's lifetime is controlled by the dedicated thread
(meaning only one thread is allowed to Free() it);

But when, during the course of Queue()d callback,
I FreeAndNil() the object that had the pointer to the callback
procedure, I get SIGSEGV pointing at CheckSynchronize in Application.
(address F0F0F0F0).

I guess, easiest way to avoid this, is 'don't do that', so i did, and
the SIGSEGV went away.

Hence the question is, how can I know it is safe to free the object,
that has the callback subscribed to it?
One of the ways would be, to have a flag in that object, that the
callback proc will set, and so the thread will know it's now safe to
Free(andNil)() it.

But are there any other, better?

Thanks in advance,
-L.

On 14-Jun-17 14:01, Lukasz Sokol via Lazarus wrote:

> On 14/06/17 11:33, Mattias Gaertner via Lazarus wrote:
>> On Wed, 14 Jun 2017 11:12:11 +0100
>> Lukasz Sokol via Lazarus <[hidden email]> wrote:
>>
>>> [...]
>>>        SomeClassLockedList := SomeClassList.LockedList; // there are try/excepts around all here, but did not want to muddle the picture
>>>        Queue(TSomeClass(SomeClassLockedList.Items[i]).Processed);
>>
>> Queue(@TSomeClass(SomeClassLockedList.Items[i]).Processed);
>>        ^
>>
>> Mattias
>>
>
> Thanks! looks so easy in hindsight ;) same the callbacks need to be assigned with the @ in front :)
>
> -L.
>


--
_______________________________________________
Lazarus mailing list
[hidden email]
http://lists.lazarus-ide.org/listinfo/lazarus
Reply | Threaded
Open this post in threaded view
|

Re: [Lazarus] Enqueuing a callback from a thread via other class - or am I overcomplicating this ?

Free Pascal - Lazarus mailing list
On Sat, Jun 17, 2017 at 11:25 PM, el es via Lazarus
<[hidden email]> wrote:
> But when, during the course of Queue()d callback,
> I FreeAndNil() the object that had the pointer to the callback procedure, I
> get SIGSEGV pointing at CheckSynchronize in Application. (address F0F0F0F0).

I don't know the details of this issue but address F0F0F0F0 means you
used compiler flag -gt (trash variables) and the variable is
uninitialized or already freed.
Yes, the flag -gt is good, helps find such errors.


> Hence the question is, how can I know it is safe to free the object,
> that has the callback subscribed to it?
> One of the ways would be, to have a flag in that object, that the
> callback proc will set, and so the thread will know it's now safe to
> Free(andNil)() it.

A flag in an uninitialized or freed object does not help.

Juha
--
_______________________________________________
Lazarus mailing list
[hidden email]
http://lists.lazarus-ide.org/listinfo/lazarus
Reply | Threaded
Open this post in threaded view
|

Re: [Lazarus] Enqueuing a callback from a thread via other class - or am I overcomplicating this ?

Free Pascal - Lazarus mailing list
On 18-Jun-17 11:20, Juha Manninen via Lazarus wrote:

> On Sat, Jun 17, 2017 at 11:25 PM, el es via Lazarus
> <[hidden email]> wrote:
>> But when, during the course of Queue()d callback,
>> I FreeAndNil() the object that had the pointer to the callback procedure, I
>> get SIGSEGV pointing at CheckSynchronize in Application. (address F0F0F0F0).
>
> I don't know the details of this issue but address F0F0F0F0 means you
> used compiler flag -gt (trash variables) and the variable is
> uninitialized or already freed.
> Yes, the flag -gt is good, helps find such errors.
>

It came I think, with default build settings in Lazarus 1.6.(2 i think)
I installed recently

>
>> Hence the question is, how can I know it is safe to free the object,
>> that has the callback subscribed to it?
>> One of the ways would be, to have a flag in that object, that the
>> callback proc will set, and so the thread will know it's now safe to
>> Free(andNil)() it.
>
> A flag in an uninitialized or freed object does not help.
>

No, I mean, the callback (executed in main thread context) must not
indicate that the object is ready to be freed (because the thread
managing the objects' lifetime may free it before the callback returns).

Hence the object, would have to have ITS callback routine be something
like a TNotifyEvent ( procedure(AObject: TSomeObject) of object, or
something), and the flag set is set when the callback returns to the
object context like

procedure TSomeObject.Callback;
// this procedure is enQueue()d by the thread
begin
   if Assigned({Self.}FSomeObjectNotifyEvent) then
     FSomeObjectNotifyEvent(Self);
    // this is what gets executed in the MainForm

   // but then we come back here right ?
   // but then again setting the CanDestruct flag here
   // may be still too early...
   /// so I don't know...
   // unless - may there be any procedure/call I can place in
   // the FSomeObjectNotifyEvent handler, that will cause the
   // execution to NOT come back here? the reverse of 'fork'...
end;

> Juha
>

Thanks in advance
-L.

--
_______________________________________________
Lazarus mailing list
[hidden email]
http://lists.lazarus-ide.org/listinfo/lazarus
Reply | Threaded
Open this post in threaded view
|

Re: [Lazarus] Enqueuing a callback from a thread via other class - or am I overcomplicating this ?

Free Pascal - Lazarus mailing list
El 18/06/2017 a las 22:44, el es via Lazarus escribió:

> Hence the object, would have to have ITS callback routine be something
> like a TNotifyEvent ( procedure(AObject: TSomeObject) of object, or
> something), and the flag set is set when the callback returns to the
> object context like
>
> procedure TSomeObject.Callback;
> // this procedure is enQueue()d by the thread
> begin
>    if Assigned({Self.}FSomeObjectNotifyEvent) then
>      FSomeObjectNotifyEvent(Self);

Hello,

Just exit the procedure and there is not free problem. You will get an
exception when you use object data, not object code.


procedure TSomeObject.Callback;
// this procedure is enQueue()d by the thread
begin
    if Assigned({Self.}FSomeObjectNotifyEvent) then
      FSomeObjectNotifyEvent(Self);
    Self.CanBeDestroyed := true;
    Exit;
end;

After you set CanBeDestroyed to true you must consider all data as
RANDOM, so don't access any property or variable.

Remember that creating and destroying threads is very expensive in CPU time.

--

--
_______________________________________________
Lazarus mailing list
[hidden email]
http://lists.lazarus-ide.org/listinfo/lazarus
Reply | Threaded
Open this post in threaded view
|

Re: [Lazarus] Enqueuing a callback from a thread via other class - or am I overcomplicating this ?

Free Pascal - Lazarus mailing list
On 19/06/17 00:30, José Mejuto via Lazarus wrote:

> El 18/06/2017 a las 22:44, el es via Lazarus escribió:
>
>> Hence the object, would have to have ITS callback routine be something like a TNotifyEvent ( procedure(AObject: TSomeObject) of object, or something), and the flag set is set when the callback returns to the object context like
>>
>> procedure TSomeObject.Callback;
>> // this procedure is enQueue()d by the thread
>> begin
>>    if Assigned({Self.}FSomeObjectNotifyEvent) then
>>      FSomeObjectNotifyEvent(Self);
>
> Hello,
>
> Just exit the procedure and there is not free problem. You will get an exception when you use object data, not object code.
>

I did now, and also, beforehands I did not have the TSomeObjectNotifyEvent() type of callback...
so in the callback I had to browse through the list of TSomeObject's, searching for the right one - and that was really risky;



>
> procedure TSomeObject.Callback;
> // this procedure is enQueue()d by the thread
> begin
>    if Assigned({Self.}FSomeObjectNotifyEvent) then
>      FSomeObjectNotifyEvent(Self);
>    Self.CanBeDestroyed := true;
>    Exit;
> end;
>
> After you set CanBeDestroyed to true you must consider all data as RANDOM, so don't access any property or variable.
>
Understood, I ensured there is nothing accessing the TSomeObject sent as Self to the event handler, after it comes back.


> Remember that creating and destroying threads is very expensive in CPU time.
>

There is only 2 statically defined threads in the entire application :)

one is the GUI thread (the Application, aka main thread)

and the other is the TSomeObject life handler
(you could call it a queue of objects to be processed and returning results from hardware communication)

Thanks,

-L.




--
_______________________________________________
Lazarus mailing list
[hidden email]
http://lists.lazarus-ide.org/listinfo/lazarus
Reply | Threaded
Open this post in threaded view
|

Re: [Lazarus] Enqueuing a callback from a thread via other class - or am I overcomplicating this ?

Free Pascal - Lazarus mailing list
In reply to this post by Free Pascal - Lazarus mailing list
On 17.06.2017 22:25, el es via Lazarus wrote:
> Where does the call queued from a thread, return to ?
 From the POV of the application programmer: "nowhere". it's just
another (main-Thread-) "Event" that (like "OnClick") gets "fired" by the
Lazarus/fpc infrastructure and is done.
> The object's lifetime is controlled by the dedicated thread
> (meaning only one thread is allowed to Free() it);
Of course a queued event should not use an object that might get freed
by other means.

If you want to pass data to the event, a useful way to avoid this is to
create a transfer object:

Type TTransfer = class; procedure the_event; TData: the_data; .....

running in the WorkerThread:

Transfer := TTransfer.Create(...)
Transfer.the_data. ..... := ....
queue(@Transfer.the_event);

(no Transfer.Free in the thread !!!)



running in the MainThread:

procedure TTransfer.the_event()
begin
...
fetch something from the_data
...
Free;      (this is Actually Self.Free; )
end;


-Michael

--
_______________________________________________
Lazarus mailing list
[hidden email]
http://lists.lazarus-ide.org/listinfo/lazarus
Reply | Threaded
Open this post in threaded view
|

Re: [Lazarus] Enqueuing a callback from a thread via other class - or am I overcomplicating this ?

Free Pascal - Lazarus mailing list
In reply to this post by Free Pascal - Lazarus mailing list
On 19/06/17 10:05, Lukasz Sokol via Lazarus wrote:

> On 19/06/17 00:30, José Mejuto via Lazarus wrote:
>> El 18/06/2017 a las 22:44, el es via Lazarus escribió:
>>
>>> Hence the object, would have to have ITS callback routine be something like a TNotifyEvent ( procedure(AObject: TSomeObject) of object, or something), and the flag set is set when the callback returns to the object context like
>>>
>>> procedure TSomeObject.Callback;
>>> // this procedure is enQueue()d by the thread
>>> begin
>>>    if Assigned({Self.}FSomeObjectNotifyEvent) then
>>>      FSomeObjectNotifyEvent(Self);
>>
>> Hello,
>>
>> Just exit the procedure and there is not free problem. You will get an exception when you use object data, not object code.
>>
>
> I did now, and also, beforehands I did not have the TSomeObjectNotifyEvent() type of callback...
> so in the callback I had to browse through the list of TSomeObject's, searching for the right one - and that was really risky;
>
>
>
>>
>> procedure TSomeObject.Callback;
>> // this procedure is enQueue()d by the thread
>> begin
>>    if Assigned({Self.}FSomeObjectNotifyEvent) then
>>      FSomeObjectNotifyEvent(Self);
>>    Self.CanBeDestroyed := true;
>>    Exit;
>> end;
>>
>> After you set CanBeDestroyed to true you must consider all data as RANDOM, so don't access any property or variable.
>>
> Understood, I ensured there is nothing accessing the TSomeObject sent as Self to the event handler, after it comes back.
>

I also had dual-usage of the TSomeObjects: sometimes, they were supposed to be 'just' processed by the thread and then discarded,
some were supposed to be with callback to the main thread;

But I was still hitting the F0F0F0F0 when the above was being queued, when FSomeObjectNotifyEvent was nil;

So actually, I had to move the (equivalent of)

SomeObject.CanBeDestroyed := true;

to be the very last call of the  FSomeObjectNotifyEvent(Self) handler, in the main thread,
to handle the ones with callback (enclosed in finally...end),

and in the worker thread, to only Queue() the callbacks if the handler is subscribed to:

if Assigned(SomeObject.FSomeObjectNotifyEvent) then
  Queue(SomeObject.FSomeObjectNotifyEvent);

> Thanks,
>
> -L.



--
_______________________________________________
Lazarus mailing list
[hidden email]
http://lists.lazarus-ide.org/listinfo/lazarus