spinettaro / delphi-event-bus Goto Github PK
View Code? Open in Web Editor NEWDelphi Event Bus (for short DEB) is an Event Bus framework for Delphi
License: Apache License 2.0
Delphi Event Bus (for short DEB) is an Event Bus framework for Delphi
License: Apache License 2.0
My app uses DEB heavily, pulling data from multiple industrial devices and posting to channels for events to operate on the data. This includes the app's main form, which displays an overview of the devices so it's processing dozens of posted events per second, and other forms that may be displayed at the same time with different views of the data.. If the user changes the program's theme, subsequent Posts to any form with registered events all throw an exception - I assume this is because of the underlying window re-creation during the theme change breaks something DEB depends on.
It would seem that a global Active property for the event engine, that suspended all events when False (i.e. Posts would be swallowed and discarded rather than processed) may work around this sort of situation, and would be a great addition. As of now, individually unregister/re-registering each form/frame that's handling events around the theme change is a huge effort, and forcing the user to not see their theme change request until the app is re-started is pretty unfriendly.
Or if you have a better suggestion, I'd love to hear it. Thanks!
Steve
I've been using DEB for many years and I LOVE it!
However, I must say, after the way of defining an event changed from TObject to interface, it's no longer as handy as before.
With the old TObject-based events, it's very easier to define an event - just define a class.
But with the latest version, for every single event/message, you have to define both:
So I have been sticking to the old TObject-based events and I'm more than happy with it ;)
The cloned LEvent local variable is not freed.
My partial (read only handling the freeing when in main-thread mode):
for LSubscription in LSubscriptions do
begin
LEvent := CloneEvent(AEvent);
PostToSubscription(LSubscription, LEvent, LIsMainThread);
// edwin: fix memory leaks
if AEventOwner and (LSubscription.SubscriberMethod.ThreadMode in [Posting, Main]) then
LEvent.Free;
// edwin end.
end;
But my code doesn't free the LEvent var when in async mode...
Firemonkey project is working well on Windows platform but will fail on Android.
Is there any chance for getting support of mobile platforms by DEB?
A useful feature will be to allow the Context to be defined on registration (RegisterSubscriberForEvents and RegisterSubscriberForChannels) if set and override the attribute especially when using MDI forms that get events for specific MDI form.
The code in https://github.com/spinettaro/delphi-event-bus/blob/master/source/EventBus.Helpers.pas#L20 is not from me - I just did an edit on that answer
It would be nice if SubscribeAttribute was looking for more than just public methods.
Thats a rather large sand obscure dependency for the little bit of code you rely on. Can you maybe implement the required yourself and move away from needing DUnitX altogether?
Thread-safe event broadcasting - this is a great concept, I don't know how do you achieve that without using Windows handle ( I assume you don't, since it supports FMX).
Most important - do you have a plan to support XE4?
if posted event object contains a TList, that TList arrive empty to subscribers.
I make a simple application to demostrate that
TestDEBIssue.zip
Place a breakpoint on frmMain, on line 62
Evalueting AEvent.List result this
On Delphi 10.4 Sydney
(((('test', False, 20), nil, nil, nil), 1, $850538, $32F9310, nil, nil), Pointer($863C4C) as {System.Generics.Defaults}IComparer<frmMain.TTestObject>, (nil,nil), 1)
On Delphi 11.0 Alexandria
((nil, nil, nil, nil), 1, $7C4004, $30E4AA0, nil, nil, Pointer($7D7D58) as {System.Generics.Defaults}IComparer<frmMain.TTestObject>, (nil,nil))
As you can see, on Alexandria the TList part arrive filled with nils
Hi Guys,
I try use but error [dcc32 Error] EventBus.pas(272): E2003 Undeclared identifier: 'TEventBusFactory'
Show in unit Eventub
function GlobalEventBus: IEventBus;
begin
Result := TEventBusFactory.GlobalEventBus;
end;
The error is on my project because i do a pompile from your samples and no show errors
Can you provide a ThreadMode:
The subscriber method will be invoked in the thread the RegisterSubscriberForEvent be called.
or
Alternatively, when calling RegisterSubscriberForEvent, we can specify the thread the subscriber method will be invoked
In order to compile the vclmessaging
sample project, I had to change all Result := []
occurrences to SetLength(Result, 0)
.
Hi,
at the moment, the Event Bus creates a global (default) event bus to manage the the communication. This means that every time a message is being post, all the subscribers receive it and react on it.
May I suggest to create something like groups of subscribers?
They can be registered like this:
TEventBus.GetDefault.RegisterSubscriber('Group 1', self);
and send messages like this:
TEventBus.GetDefault.post('Group 1', LEvent);
An observer can be, also, subscribed like this:
[Subscribe ('Group 1')]
procedure OnEvent(AEvent: TAnyTypeOfEvent);
begin
// manage the event
end;
Before I try to do it myself, I wanted to check with you if this is something that you would be interested in bringing into your project.
Thanks
This doesn't seem to be fixed, even with the latest commit as of Aug 18, 2020. When TEventMM = mmManual or mmManualAndFreeMainEvent, LEvent is NEVER properly free-ed, and will result in memory leak!
The test cases do not cover mmManual and mmManualAndFreeMainEvent
Originally posted by @wxinix in #10 (comment)
There are threading issues in asynchronous messaging with inactive subscription.
Namely code in Post methods checks whether subscription is active before invoking subscription method
TEventBus.Post
for LSubscription in LSubscriptions do begin
if (LSubscription.Context <> AChannel) or (not LSubscription.Active) then Continue;
PostToChannel(LSubscription, AMessage, LIsMainThread);
for LSubscription in LSubscriptions do begin
if not LSubscription.Active then Continue;
PostToSubscription(LSubscription, AEvent, LIsMainThread);
But, depending on threading mode PostToChannel and PostToSubscription methods can invoke subscription method asynchronously with TThread.Queue or TTask.Run or TThread.CreateAnonymousThread
During that time subscription can get inactive and that can cause crashes by calling subscription method on dead object.
Hi,
I hope this is the right place to ask a question. Feel free to remove this false "issue".
I used the deb protocol some years ago in java with a small "single" page application. I think it is an elegant solution to an hard architectural problem.
I built an MVVM structure in Delphi with your deb pattern library.
How would you solve this (the problem is simplified):
Assumption: One ViewModel (called vmA) with three Views and One ViewModel (called vmB) with 2 views.
The [Subscribe(...)] is positioned in a "master view" class of the framework and the five views inherits from it.
The master view subscribe to "viewmodel.topic" an avent called "OnViewModelCalling".
The ViewModels calls the post() to the "viewmodel.topic".
How can I be notified who is the "Sender" ViewModel of the event? I mean vmA or vmB?
Do I add these infos in the event object or are there any other solutions?
One solutions without object sender I am thinking:
One "topic" per "sender" (viewmodel.topic.vmA and viewmodel.topic.vmB). But I have to move the [Subscribe(...)] to children and the responsibility of MVVM comunication system is passed to the concrete class. I don't like this.
Thanks
Eddy
I started testing the DEB code and I have a few doubts:
In the examples (e.g. weather station) in the event handling methods, event objects are not released, there is a continuous memory leakage.
Why aren't cloned event objects released by the event bus? The bus could do this when the event handling method is returned.
I don't know why there are two identical methods:
function TEventBus.GenerateThreadProc(ASubscription: TSubscription;
AEvent: TObject): TThreadProcedure;
start
Result := procedure
start
if ASubscription.Active then
start
ASubscription.SubscriberMethod.Method.Invoke(ASubscription.Subscriber,
[AEvent]);
end;
end;
end;
function TEventBus.GenerateTProc(ASubscription: TSubscription;
AEvent: TObject): TProc;
start
Result := procedure
start
if ASubscription.Active then
start
ASubscription.SubscriberMethod.Method.Invoke(ASubscription.Subscriber,
[AEvent]);
end;
end;
end;
I believe there is a small bug in the Unregister mechanism:
if it takes a Unregister, all those who have made a subscription for that class of events are removed.
I think the problem is the 290-292 lines of the unit EventBus.pas, where instead of:
if (LSubscription.Subscriber.Equals (ASubscriber)) then
LSubscription.Active: = false;
LSubscriptions.Delete (I);
put it:
if (LSubscription.Subscriber.Equals (ASubscriber)) then
begin
LSubscription.Active: = false;
LSubscriptions.Delete (I);
end;
But it is better if you look at the code...
Congratulations for the good work.
Hello,
Take a look at the following articles! I think DEB can benefit from this. (it has 4 parts)
https://www.thedelphigeek.com/2020/11/readers-writer-lock-part-1-why.html
Thank you
TEventBus.Post makes a clone of the event, and this clone is never freed. This causes a build up of event objects in memory.
Add the following to EventBus.pas which will decrement the interface counter on finalization (if set) and release allocated memory. This specifically helps in situations where your using a memory manager which reports memory leaks on shutdown.
finalization
FGlobalEventBus := nil;
end.
I shared this simply-designed event bus library in the Delphi G+ group, here: https://plus.google.com/115143382472228750230/posts/ewvvesqjCb7
Some people reported that there might be a thread-safe with the TEventBus.GetDefault:
singleton implementation, I thought I should post it here :)
As I said, the usage of this library is very clean and elegant, I'm definitely will use it in my projects :)
Given the possibility to create multiple event bus instances and the non existence of any other class vars I think the lock should be instance wise and not the same lock for possibly multiple instances.
Hi,
the cloned event in Post is not freed. I have corrected that.
How do I push on github?
Hello Daniele,
So I've just started integrating delphi-event-bus into my program.
The first issue I'm experiencing is that, TEventBus.Post
will be in a dead-loop in line 162, which calls TRTTIUtils.Clone
, which in turn loop infinitely in line 827:
if Field.GetValue(cloned).IsEmpty then
begin
targetObject := TRTTIUtils.Clone(sourceObject); // infinite loop here...
I guess it must has something to do with my event definition:
// Base event about a TWoDoc
TBaseDocEvent = class(TBaseAppEvent)
private
FDoc: TWoDoc;
public
property Doc: TWoDoc read FDoc write FDoc;
end;
Where TWoDoc has properties like
property ParentDoc: TWoDoc read FDoc write FDoc;
property NextSibling: TWoDoc read FDoc write FDoc;
// and so on...
I wish you can shed some light on the issue, thanks.
It would be very usefull if Context type in TSubscriberMethod can be defined by developer , something like this
//EventBus.Develoer //new unit
const
TSubscriberContextDefault = ''; //could change to TSubscriberContextDefault = 0
type
TSubscriberContextType = string; //could change to TSubscriberContextType = integer; or even flat to carry two part
//EventBus.Subscribers unit
type
TSubscriberMethod = class(TObject)
private
FEventType: TClass;
FThreadMode: TThreadMode;
FMethod: TRttiMethod;
FContext: TSubscriberContextType;
...
it needed to change some lines in other units
my (and some friends) implementation of events is based on integer and a long list of integer constants and methods that could use this perfect event bus implementation
thank you.
Dear Danielle.
I try to process as much as possible in background. The event bus is perfect for simplifying my code. I'm using some additional libraries for some reason, and I get synchronized (Delphi-) events from those libraries. I would like to post some of the processing into background, but unfortunately the eventbus post gets into deadlock. I prepared a simplified test for reproduction. As a workaround I removed the critical section from your code in post routine, because I can't see any disadvantage. But maybe you can explain, what you wanted to avoid by using the critical section in the post routine?!
EventBusTest.zip
Kind regards,
Detlef Schweng.
Hello,
It seems the intefaces version of DEB does not work with datamodules ?
Attached sample project.
Thank you
DEB.Problem.zip
Hi,
Do you have any idea why the following error might happen? It seems happens when duplicating the event object. Currently I workaround it using the TObjectClone
class (shown at the end), but I really want my copy of delphi-event-bus to be fully in sync with the official repository, otherwise it's really a pain when committing changes.
compiled with : Delphi XE4
madExcept version : 4.0.19
callstack crc : $e81d85aa, $52a6acdb, $8ac0e71b
exception number : 1
exception class : EAccessViolation
exception message : Access violation at address 00C6882F in module 'Program1.exe'. Read of address 00000000.
main thread ($3850):
00c6882f +02b Program1.exe DuckListU 169 +1 TDuckTypedList.CanBeWrappedAsList
00d4ba7c +220 Program1.exe RTTIUtilsU 589 +31 TRTTIUtils.CopyObject
00d4c39f +377 Program1.exe RTTIUtilsU 833 +59 TRTTIUtils.Clone
00d4c351 +329 Program1.exe RTTIUtilsU 827 +53 TRTTIUtils.Clone
00e5b203 +00f Program1.exe EventBus 89 +8 TEventBus.CloneEvent
00e5bc8a +0e6 Program1.exe EventBus 204 +20 TEventBus.Post
0102510f +067 Program1.exe WorkSpaceForm 757 +3 TfrmWorkSpace.TryOpenMostRencelyProject$237291$ActRec.$1$Body
00929d3a +08e Program1.exe OtlTaskControl 4220 +7 TOmniMessageExec.OnMessage
00926969 +1c1 Program1.exe OtlTaskControl 3170 +18 TOmniTaskControl.ForwardTaskMessage
0040fc8a +012 Program1.exe System 34138 +12 @IntfCast
00929707 +03b Program1.exe OtlTaskControl 4114 +1 TOmniTaskControlEventMonitor.ForwardTaskMessage
0092b837 +07f Program1.exe OtlEventMonitor 330 +6 ProcessMessages
0092ba49 +0a1 Program1.exe OtlEventMonitor 363 +7 TOmniEventMonitor.WndProc
007f562f +07f Program1.exe DSiWin32 6175 +15 DSiClassWndProc
762e7885 +00a USER32.dll DispatchMessageW
006a2143 +0f3 Program1.exe Vcl.Forms 10288 +23 TApplication.ProcessMessage
006a2186 +00a Program1.exe Vcl.Forms 10318 +1 TApplication.HandleMessage
006a24c1 +0c9 Program1.exe Vcl.Forms 10456 +26 TApplication.Run
010df31a +0f2 Program1.exe Program1 150 +44 initialization
76cc343b +010 kernel32.dll BaseThreadInitThunk
My workaround (I got TObjectClone from the Internet but forgot the source :p):
class function TObjectClone.From<T>(Source: T): T;
var
Context: TRttiContext;
IsComponent, LookOutForNameProp: Boolean;
RttiType: TRttiType;
Method: TRttiMethod;
MinVisibility: TMemberVisibility;
Params: TArray<TRttiParameter>;
Prop: TRttiProperty;
SourceAsPointer, ResultAsPointer: Pointer;
begin
RttiType := Context.GetType(Source.ClassType);
//find a suitable constructor, though treat components specially
IsComponent := (Source is TComponent);
for Method in RttiType.GetMethods do
if Method.IsConstructor then
begin
Params := Method.GetParameters;
if Params = nil then Break;
if (Length(Params) = 1) and IsComponent and
(Params[0].ParamType is TRttiInstanceType) and
SameText(Method.Name, 'Create') then Break;
end;
if Params = nil then
Result := Method.Invoke(Source.ClassType, []).AsType<T>
else
Result := Method.Invoke(Source.ClassType, [TComponent(Source).Owner]).AsType<T>;
try
//many VCL control properties require the Parent property to be set first
if Source is TControl then TControl(Result).Parent := TControl(Source).Parent;
//loop through the props, copying values across for ones that are read/write
Move(Source, SourceAsPointer, SizeOf(Pointer));
Move(Result, ResultAsPointer, SizeOf(Pointer));
LookOutForNameProp := IsComponent and (TComponent(Source).Owner <> nil);
if IsComponent then
MinVisibility := mvPublished //an alternative is to build an exception list
else
MinVisibility := mvPublic;
for Prop in RttiType.GetProperties do
if (Prop.Visibility >= MinVisibility) and Prop.IsReadable and Prop.IsWritable then
if LookOutForNameProp and (Prop.Name = 'Name') and
(Prop.PropertyType is TRttiStringType) then
LookOutForNameProp := False
else
Prop.SetValue(ResultAsPointer, Prop.GetValue(SourceAsPointer));
except
Result.Free;
raise;
end;
end;
Can not Post the following class:
TOnNotifyDocEvent = class
private
FData: TDictionary<string, TValue>;
public
constructor Create;
destructor Destroy;override;
property Data: TDictionary<string, TValue> read FData;
end;
constructor TOnInfoNotifyDocDataLoadCompleteEvent.Create;
begin
FData:= TDictionary<string, TValue>.Create;
end;
Hello,
I recently discovered Tiny.RTTI, according to its just-commited benchmark demo program, as shown on the repository's github page, it's 500+ faster than System.Rtti
! I tried to recreate the
I've no idea why it's that faster, maybe people as knowledgeable as @sglienke can tell us something :)
And since DEB invokes the subscribing methods using System.Rtti
, so it might be a candidate for improving the performance.
Hi,
I'm using Delphi Tokyo, latest version. I'm using Spring4D container and declaring an IEventBus (not using the built in singleton) as a singleton.
In one part of my code I call twice the method to UnRegister the current object.
E.g.
if not Assigned(fEventBus) then Exit; // shouldnt be needed but, hey just in case
If fEventBus.IsRegistered(Self) then
fEventBus.UnRegister(Self);
Calling this twice will cause an exception. The exception happens while attempting to Acquire the critical section via FCS. It seems that the finalization section was already called and the FCS critical section is now nil, causing the issue.
In general Finalization sections mixed with Dependency containers will cause trouble. I will suggest an implementation that completely skips the use of that methodology if the developer is not using the built in singleton.
Thank you for your great work with this, it is extremely useful. It will be great if you wrap it into a component so its easier to share and it can be exposed via Delphinus or GetIt.
UPDATE:
I did a quick test changing the Critical sections with a TMonitor (new versions are faster than TCriticalSections due to use of Spinlocks) and it did the trick. Another option could be to put the critical section inside of the object and only use the global one when creating the GetDefault singleton.
Deb uses TTaskRun, which submits to a shared threadpool. When multiple events are fired quickly, the app deadlocks and does not recover. I have fixed this by creating a threadpool for DEB and modifying the run to use that threadpool.
Before:
TTask.Run(LProc);
After:
TTask.Run(LProc, FDebThreadPool);
Pull request for fix is: #74
If anyone has an example of using TThreadPoolStats to check the health of that thread pool before the task runs it would be great to see to ensure the pool is healthy enough not to deadlock later.
Hello,
I've just write a little info here: Some notes on using delphi event bus
But I don't know how to contribute it back to this master repository, any help :)
The Delphi-Event-Bus library works great for pub/sub pattern. Is that possible to use the library for request/reply pattern?
Please add the parameter ARaiseExcIfEmpty : Boolean to the RegisterSubscriber() method to pass it on to the FindSubscriberMethods() method.
At the moment, in the RegisterSubscriber() method, when calling the FindSubscriberMethods() method, this parameter is hardcoded to True, it should not be so, see:
procedure TEventBus.RegisterSubscriber<T>(ASubscriber: TObject); <== please add ARaiseExcIfEmpty param
var
LSubscriberClass: TClass;
LSubscriberMethods: TArray<TSubscriberMethod>;
LSubscriberMethod: TSubscriberMethod;
begin
FMultiReadExclWriteSync.BeginWrite;
try
LSubscriberClass := ASubscriber.ClassType;
LSubscriberMethods := TSubscribersFinder.FindSubscriberMethods<T>(LSubscriberClass, True); <==== HARDCODED
for LSubscriberMethod in LSubscriberMethods do Subscribe<T>(ASubscriber, LSubscriberMethod);
finally
FMultiReadExclWriteSync.EndWrite;
end;
end;
I have a problem with this because I have a base class that supports caching and I want to call RegisterSubscriberForEvents() in this base class (not to do this in many inherited classes). But because not every descendant cache class uses events, I get an EObjectHasNoSubscriberMethods exception in runtime.
And of course a change is necessary in these two methods
procedure TEventBus.RegisterSubscriberForChannels(ASubscriber: TObject); <== ARaiseExcIfEmpty param
begin
RegisterSubscriber<ChannelAttribute>(ASubscriber);
end;
procedure TEventBus.RegisterSubscriberForEvents(ASubscriber: TObject); <== ARaiseExcIfEmpty param
begin
RegisterSubscriber<SubscribeAttribute>(ASubscriber);
end;
Hi Folks,
I'm just starting to use delphi-event-bus and I'd like to have some more information on the subscription types, like typical use cases and when to use which subscription method.
I assume, but I'm not 100% sure:
What about the others?
Hello,
I want to know how I can use the "Active" property of TSubscription? I don't see any method to put them out of my program code. What is the correct way to disable the notification?
Hi,
I use a MQTTClient and onReceive I post a event for a lot of subscriber on my code.
procedure TDMMain.riempiDatiMqtt(ADevice, APayLoad : string);
var
LEvent: IMQTTReceiveEvent;
begin
try
LEvent := GetMQTTReceiveEvent;
LEvent.SN := ADevice;
LEvent.Payload := APayLoad;
ToCodeSite('LEvent',LEvent.SN + ' ' + LEvent.Payload);
try
GlobalEventBus.Post(LEvent);
except
on e : exception do
begin
ToCodeSite('GlobalEventBus.Post(LEvent) except: ', e.Message);
end;
end;
except
on e: Exception do
begin
ToCodeSite('RiempiDatiMqtt except: ', e.Message);
end;
end;
end;
Rarely, the GlobalEventBus.Post method raises a typecast error like subject with different type of Subscriber class. like TAniindicator, TStyledEdit, TEdit, and more.
Can you help me?
[dcc32 Warnung] EventBus.Helpers.pas(215): W1000 Symbol 'DisposeOf' ist veraltet: 'Use Free instead'
class destructor TInterfaceHelper.Destroy;
begin
FInterfaceTypes.DisposeOf;
end;
Any good reason why DisposeOf should still be used?
Hello,
There four event-handler invocation mode:
TThreadMode = (Posting, Main, Async, Background);
I use only the default (Posting
) mode and Main
mode (ensure the code to be run in the UI thread if it needs to access any UI controls).
But I don't quite understand the rest of the modes, care to explain a little :)
Is it reasonable to make the TEventBus.Post
method be able to return a value (can be any type of value, a TObject or TValue? not sure)?
The idea is like the `SendMessage' winapi.
It's very necessary, in case we use an event to represent an action (a concept borrowed from Redux action.
That being said, I want to achieve an application architecture, where:
the modification of the model is centralized in a single module (which will subscribe events from other modules - including, but not limited to the UI),
and the other modules will not modify the model directly, but only post through the EventBus "action events" to describe what they desire the model to change.
And it must come in handy if the event posting would be able to return values when posting to the same thread.
Not sure if I have described it clearly, but I mainly have inspired by Redux Framework.
I hope this makes sense.
And this stackoverflow question might help understanding my suggestion: React.js - flux vs global event bus
Uses System.JSON
in new versions while using Data.DBXJSON
in older versions, for example, XE4.
Thanks.
This eliminates unnecessary extra code that does reference counting or copying.
Hi,
I created a new app, included EventBus and then ran it on an Android device and it started regularly.
Right after I added System.Messaging, I run and 8 times out of 10 it doesn't start and stays on the splash screen.
BR
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.