Closed carmas123 closed 9 years ago
First of all, you cannot directly instantiate TLKEventThread
. It is now defined as a Abstract
(and should always have been, sorry for that).
I suggest you take a look at the section on Defining an Event Thread in the LKSL Wiki to see how you need to set up your TLKEventThread properly.
Without seeing the declaration of TfrmeBase
, I cannot tell what type you're inheriting it from.
I can say that your line EvT_ArchiveItemSelectedEvent := TLKEventThread.Create();
is a significant problem (as I mentioned earlier, you cannot create an instance of TLKEventThread
itself... you can only inherit from it in a descendant class as shown in the Wiki entry linked above).
Please reply back to let me know once you've redone the code to conform to that shown in the Wiki :)
I'm having a little difficulty trying to understand what it is you're attempting to do here, by the way.
procedure TfrmeBase.ItemSelectedEvent(const AEvent: TArchiveItemSelectedEvent);
begin
// Virtual Method (EVENT FIRED)
end;
^ This doesn't show me what you're looking to do with the Event once it gets triggered for processing on the Event Thread.
If you want to create an instance of your Event Thread for each TForm
instance, you'll need to make the Event Thread a member of the Form.
If, however, you want a TForm
descendant instance to be created each time the Event occurs, you'll need to Synchronize
the ItemSelectedEvent
method. This is because ItemSelectedEvent
is being executed in the context of the Event Thread, not the GUI Thread.
Example:
procedure TfrmeBase.ItemSelectedEvent(const AEvent: TArchiveItemSelectedEvent);
begin
Synchronize(procedure begin
Application.CreateForm(TMyForm, MyForm);
end);
end;
^ Also, don't forget that this code would cause issues because there's only one MyForm
container, and you'd be disassociating the previous TMyForm
instance each time ItemSelectedEvent
is fired. However, since each TMyForm
instance would be owned by the Application
object, there would be no memory leaks.
Now this is my unit
type
TArchiveItemSelectedEvent = class(TLKEvent)
private
FArchiveClass: TArchiveClass;
public
constructor Create(AArchiveClass: TArchiveClass); reintroduce;
property ArchiveClass: TArchiveClass read FArchiveClass;
end;
TArchiveItemSelectedEventListener = class(TLKEventListener<TArchiveItemSelectedEvent>)
end;
TArchiveItemSelectedThread = class(TLKEventThread)
end;
var
EvT_ArchiveItemSelectedEvent: TArchiveItemSelectedThread = nil;
implementation
constructor TArchiveItemSelectedEvent.Create(AArchiveClass: TArchiveClass);
begin
inherited Create;
FArchiveClass := AArchiveClass;
end;
initialization
EvT_ArchiveItemSelectedEvent := TArchiveItemSelectedThread.Create();
finalization
EvT_ArchiveItemSelectedEvent.Kill;
end.
and this is my form:
type
TMyForm = class(TForm)
private
FItemSelectedListener: TArchiveItemSelectedEventListener;
procedure InitializeListeners;
procedure FinalizeListeners;
public
procedure ItemSelectedEvent(const AEvent: TArchiveItemSelectedEvent);
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
implementation
procedure TMyForm.ItemSelectedEvent(const AEvent: TArchiveItemSelectedEvent);
begin
// This is the event "fired"
end;
constructor TMyForm.Create(AOwner: TComponent);
begin
inherited;
InitializeListeners;
end;
destructor TMyForm.Destroy;
begin
FinalizeListeners;
inherited;
end;
procedure TMyForm.InitializeListeners;
begin
FItemSelectedListener := TArchiveItemSelectedEventListener.Create(EvT_ArchiveItemSelectedEvent, ItemSelectedEvent);
end;
procedure TMyForm.FinalizeListeners;
begin
FreeAndNil(FItemSelectedListener);
end;
end.
On first form created all work, but when create another form I got the exception
You should not be creating Listeners from the UI Thread! They should only be created within the context of a TLKEventThread
descendant, and should really belong to that TLKEventThread
descendant:
type
TArchiveItemSelectedEvent = class(TLKEvent)
private
FArchiveClass: TArchiveClass;
public
constructor Create(AArchiveClass: TArchiveClass); reintroduce;
property ArchiveClass: TArchiveClass read FArchiveClass;
end;
TArchiveItemSelectedEventListener = class(TLKEventListener<TArchiveItemSelectedEvent>);
TArchiveItemSelectedThread = class(TLKEventThread)
private
FItemSelectedListener: TArchiveItemSelectedEventListener;
procedure ItemSelectedEvent(const AEvent: TArchiveItemSelectedEvent);
protected
procedure InitializeListeners; override;
procedure FinalizeListeners; override;
end;
var
EvT_ArchiveItemSelectedEvent: TArchiveItemSelectedThread = nil;
implementation
{ TArchiveItemSelectedEvent }
constructor TArchiveItemSelectedEvent.Create(AArchiveClass: TArchiveClass);
begin
inherited Create;
FArchiveClass := AArchiveClass;
end;
{ TArchiveItemSelectedThread }
procedure TArchiveItemSelectedThread.InitializeListeners;
begin
FItemSelectedListener := TArchiveItemSelectedEventListener.Create(Self, ItemSelectedEvent)
end;
procedure TArchiveItemSelectedThread.FinalizeListeners;
begin
FItemSelectedListener.Free;
end;
procedure TArchiveItemSelectedThread.ItemSelectedEvent(const AEvent: TArchiveItemSelectedEvent);
begin
// Thing to do when TArchiveItemSelectedEvent occurs
end;
initialization
EvT_ArchiveItemSelectedEvent := TArchiveItemSelectedThread.Create();
finalization
EvT_ArchiveItemSelectedEvent.Kill;
end.
Following on from the reply above, if you want an Event to perform a certain action across multiple Forms, then the TLKEventThread
descendant instance should be created as a member of the Form:
type
TForm1 = class(TForm)
private
FEventThread: TArchiveItemSelectedThread;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
implementation
{ TForm1 }
constructor TForm1.Create(AOwner: TComponent);
begin
FEventThread := TArchiveItemSelectedThread.Create;
end;
destructor TForm1.Destroy;
begin
FEventThread.Kill;
end;
You would then remove the following lines from the code example in my previous response:
var
EvT_ArchiveItemSelectedEvent: TArchiveItemSelectedThread = nil;
and
initialization
EvT_ArchiveItemSelectedEvent := TArchiveItemSelectedThread.Create();
finalization
EvT_ArchiveItemSelectedEvent.Kill;
Oh yes :) this can work fine, but the problem is that I don't want a global reference to my form. I create my form in a local var and not with a global reference. How can I interact from the Thread to my form? I need to create a custom thread that reference to this?
Oh, and to hook up a procedure on your TForm
descendant to be called when its respective TArchiveItemSelectedThread
's ItemSelectedEvent
is called, we'd do something like this...
In your Event unit:
type
TItemSelectedEvent = procedure(const AEvent: TArchiveItemSelectedEvent) of object;
TArchiveItemSelectedThread = class(TLKEventThread)
private
FOnArchiveItemSelected: TItemSelectedEvent;
FItemSelectedListener: TArchiveItemSelectedEventListener;
function GetOnArchiveItemSelected: TItemSelectedEvent;
procedure SetOnArchiveItemSelected(const AOnArchiveItemSelected: TItemSelectedEvent);
procedure ItemSelectedEvent(const AEvent: TArchiveItemSelectedEvent);
protected
procedure InitializeListeners; override;
procedure FinalizeListeners; override;
public
property OnArchiveItemSelected: TItemSelectedEvent read GetOnArchiveItemSelected write SetOnArchiveItemSelected;
end;
We'd now implement the Getter and Setter like this:
function TArchiveItemSelectedThread.GetOnArchiveItemSelected: TItemSelectedEvent;
begin
Lock;
try
Result := FOnArchiveItemSelected
finally
Unlock;
end;
end;
procedure TArchiveItemSelectedThread.SetOnArchiveItemSelected(const AOnArchiveItemSelected: TItemSelectedEvent);
begin
Lock;
try
FOnArchiveItemSelected := AOnArchiveItemSelected;
finally
Unlock;
end;
end;
We'd now implement ItemSelectedEvent
like this:
procedure TArchiveItemSelectedThread.ItemSelectedEvent(const AEvent: TArchiveItemSelectedEvent);
var
LOnArchiveItemSelected: TItemSelectedEvent;
begin
LOnArchiveItemSelected := GetOnArchiveItemSelected;
if Assigned(LOnArchiveItemSelected) then
begin
Synchronize(procedure begin
LOnArchiveItemSelected(AEvent);
end);
end;
end;
To hook this up, we modify the Form's declaration like this:
TForm1 = class(TForm)
private
FEventThread: TArchiveItemSelectedThread;
procedure ItemSelectedEvent(const AEvent: TArchiveItemSelectedEvent);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
Now we complete the implementation like this:
{ TForm1 }
constructor TForm1.Create(AOwner: TComponent);
begin
FEventThread := TArchiveItemSelectedThread.Create;
FEventThread.OnArchiveItemSelected := ItemSelectedEvent;
end;
procedure TForm1.ItemSelectedEvent(const AEvent: TArchiveItemSelectedEvent);
begin
// Do whatever we need to do on this particular Form Instance.
end;
So there we go! Each time you dispatch a TArchiveItemSelectedEvent
, the ItemSelectedEvent
on each Form will be executed within the scope of that Form.
This is a very good solution!! But I've another question to you. If I have 5 events (5 distinct event's type) to manage and I have 6 or more form I need 30 threads?
but the problem is that I don't want a global reference to my form. I create my form in a local var and not with a global reference.
This is fine, the code I've provided does not require a global reference to your Form... it uses a callback method (like OnClick
etc. in the VCL) to pass the ItemSelectedEvent
call over to the Form on which that Thread has been created. This way, the Form will need to know about your Event Thread type, but not the other way around :)
If I have 5 events (5 distinct event's type) to manage and I have 6 or more form I need 30 threads?
No!
If all of the Event Types are in some way related (such as, each one performs an action on the Form like the one we've discussed above), then you simply create the additional listeners as members of that one Event Thread type:
TArchiveItemSelectedThread = class(TLKEventThread)
private
FListener1: TListener1;
FListener2: TListener2;
FListener3: TListener3;
procedure Event1Call(const AEvent: TEvent1);
procedure Event2Call(const AEvent: TEvent2);
procedure Event3CAll(const AEvent: TEvent3);
// Blah Blah Blah
end;
You would then hook up on FOnEvent
members, getters, setters and the Properties
for those just as we did before.
You'd also create each Listener in InitializeListeners
, and destroy them in FinalizeListeners
just like you do for the one you already have.
There is no limit to the number of Listeners you can have within an Event Thread type!
Also, don't forget that TLKEventThread
descendants are "good citizen" Threads!
When they aren't doing anything, they properly wait until there's work for them to perform, yielding their computation time to other Threads and consuming 0% of your CPU. This means that you can have as many Threads created (while idle) as the OS will physically allow (of course remembering to consider that each Thread will consume memory, so in 32bit you wouldn't want to create 2GB worth of Thread instances).
In my game engine, for example, I currently create just 6 threads, and only 2 of those are "always active". The rest wake up and go to sleep as and when they are required. The engine (internally) has over 200 distinct Event Types, and an actual Game Implementation on top of the Engine could have many thousands of Event Types!
Please let me know if I can consider this issue closed now :)
Only this: If I need to handle 3 types of event, I can create a 3 types of listener in a single thread?
If I need to handle 3 types of event, I can create a 3 types of listener in a single thread?
Yes! You can create as many types of Listener in a single Thread as you need :+1:
Ok, so I only need a thread for each for and all gone?
Ok, so I only need a thread for each for and all gone?
Yes, if you're simply wanting to have different Event Types execute a method on multiple instances of your Form type, then you need only create a single Event Thread as a member of that Form Type to do it. On that Event Thread type, you instantiate as many Event Listeners as you require :+1:
Ok thank you so much
You're very welcome! Glad I could help.
I've this situation: I've created an event structure like this:
My project contain some form and I create a TArchiveItemSelectedEventListener on every form:
So, all work fine when I instantiate a single form, but when I try to create a second form I got an access violation on this method:
Can you help me to solve this problem? I tried to remove the "FinalizeListener" and this exception will not raised but a critical section does not destroyed and I've a critical section for every form created.