Multicast Events - the finale

In my previous two posts I presented a technique using the new generics language feature of Delphi 2009 to create a typesafe multicast event. In the previous post, I showed how you can create a TMulticastEvent<T> instance and assign it to an event handler for an existing event on a TComponent derived type. Using the existing FreeNotification mechanism, you didn’t need to worry about explicitly freeing the multicast event object. What if one of the components in the sink event handlers in the multicast event list was freed? The good thing is that the FreeNotification mechanism works both ways. We can leverage this functionality again to handle cleanup from the other direction.

In order to implement the complete cleanup for the TComponentMulticastEvent<T>, we need to know when an event handler was added and when one was removed. To do this I added these two virtual methods to the base TMulticastEvent class (the base non-generic version). There are also helper functions, RemoveInstanceReferences() and IndexOfInstance() that can be used in descendants to remove all event handlers that refer to a specific object instance and check if a specific instance is being referenced within the list.

  TMulticastEvent = class
    ...
  strict protected
    procedure EventAdded(const AMethod: TMethod); virtual;
    procedure EventRemoved(const AMethod: TMethod); virtual;
  protected
    procedure RemoveInstanceReferences(const Instance: TObject);
    function IndexOfInstance(const Instance: TObject): Integer;
    ...
  end;

They’re not marked abstract because the immediate descendant, TMulticastEvent<T> doesn’t need to and should not be forced to override them. They just do nothing in the base class. In the corresponding Add and Remove methods on TMulticastEvent, these virtual methods are then called with event just added or just removed. Now we override the EventAdded and EventRemoved methods in the TComponentMulticastEvent<T> class:

  TComponentMulticastEvent<T> = class(TMulticastEvent<T>)
    ...
  private
    FSink: TNotificationSink;
  strict protected
    procedure EventAdded(const AMethod: TMethod); override;
    procedure EventRemoved(const AMethod: TMethod); override;
    ...
  end;

We also need to hold a reference to the internal notification sink class in order to use its FreeNotification mechanism. Here’s the implementation of these methods:

procedure TComponentMulticastEvent<T>.EventAdded(const AMethod: TMethod);
begin
  inherited;
  if TObject(AMethod.Data) is TComponent then
    FSink.FreeNotification(TComponent(AMethod.Data));
end;

procedure TComponentMulticastEvent<T>.EventRemoved(const AMethod: TMethod);
begin
  inherited;
  if (TObject(AMethod.Data) is TComponent) and (IndexOfInstance(TObject(AMethod.Data)) < 0) then
    FSink.RemoveFreeNotification(TComponent(AMethod.Data));
end;

And then the Notification on the private TNotificationSink class:

procedure TComponentMulticastEvent<T>.TNotificationSink.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if Operation = opRemove then
    if AComponent = FOwnerComp then
      Free
    else
      FEvent.RemoveInstanceReferences(AComponent);
end;

In the EventRemoved method we call IndexOfInstance() to ensure that there aren’t multiple references to the same instance in the list before we remove the free notification hook. This is because FreeNotification will add the instance to its internal list only once.

So there you go, a multicast event that also performs full auto-cleanup for both the source and the sink instances. If the source instance goes away, the multicast event instance is automatically cleaned up. Likewise, if one of the sink event handlers’ instances go away, it will automatically be removed from the list so there are no stale references. Of course, I’ll remind the reader that with this implementation, it only works for TComponent derived instances. For non-TComponent derived instances, you can still use a descendant of TMulticastEvent<T> in a manner similar to TComponentMulticastEvent<T> mixed with, for instance, a technique described here.

Posted by Allen Bauer on September 3rd, 2008 under CodeGear |



6 Responses to “Multicast Events - the finale”

  1. m. Th. Says:

    Nice thing Allen. I must confess that I didn’t follow very close your posts now due of lack of time but I intend to do it. However some notes:

    1. Imho, a message queue in TObject it is badly needed, not only for having Notification method for the above purposes but this is also a central piece in parallel programming - for ex. Actor model, Erlang, Oz programming languages etc. Having such a structure at root of the class system also gives us new perspectives in expresing inter-object interactions (think only about Windows’s messaging system and what was achieved with it…)

    2. A common usage pattern is:
    - I made my
    TFooComp = class(TComponent)
    property DataSource: TDataSource read FDataSource write FDataSource; //or whatever…

    end;

    …and I want to hook/add functionality to (some of) the FDataSource.DataSet’s events.

    How can be this done in a ’safe’ manner with your implementation, a.) without knowing nothing about the ‘outside world’ (ie. my TFooComp will be deployed/used in an unknown code) b.) allowing the possibility of using many instances of TFooComp class, preferably without having each instance its MultiCast Manager?

    HTH & TIA,

    m. Th.

  2. daniel Says:

    RE_1>This is like saying that we need a window-proc in TObject. Allen just posted an example of a (potentially) many to many relation between events and event handlers, implemented via generics (read: c++ templates)
    RE_2>Since DataSource property is known (as datatype: TDataSource) and you expressed premeditation in delegating events of DataSource.DataSet, you already know "something" (a lot) about the outside world. Since TDataSource makes composition with its DataSet property, the only way to handle what you said you would have to use notifications anyway. How can TFooComp be deployed in an "unknown code" since you already subscribed to ancestor class contract and most like to DataSource and further on to DataSource.DataSet via simple composition? (TFooComp "has a"). Of course, in your example TFooComp would rather be a descendent o TDataSource… If you look at the source of TSimpleDataset (interesting coincidence, from what I remember, Allen baptized that component many years ago…) you will see that composition (with or without delegation) go hand in hand… Generic delegation is brilliant and, since you were wondering, it’s "safe" (as in early binding, or compiler front-end checked). In a way, your "business as usual" components are dbAware controls… Just in a way… So, lets not judge the model by assuming that helpers are anything but helpers…

    Allen, Thanks again, Cool!

    Yours,
    Daniel

  3. daniel Says:

    disregard "FooComp would rather be a descendent o TDataSource"

  4. m. Th. Says:

    @Daniel:

    #1:
    Somewhat but not quite. Let’s get out from Windows box. For example see:

    http://en.wikipedia.org/wiki/Erlang_(programming_language)

    and go to "Concurrency and distribution oriented language" chapter. (there, the definition for ‘process’ is different for a regular OS process, take care).

    Also, you can have a look at

    http://en.wikipedia.org/wiki/Oz_programming_language

    "Message passing concurrency" part.

    And, perhaps, the accolade at:

    http://en.wikipedia.org/wiki/Message_passing

    (especially the "OOP" part)

    …and, yes, my request is somewhat outside of the main topic of Allen’s post.

    #2:
    Yes, nowadays we must use notifications to implement this. And we must do hard-wired/specialized/"premeditated" :-) code to do this, ie.

    if FDataSourcenil then
    if FDataSet.DataSetnil then
    begin
    FOldBeforePost:=FDataSource.DataSet.BeforePost;
    FDataSource.DataSet.BeforePost:=FNewBeforePost;
    end; //…to attach

    (and similar code to ‘detach’ it)
    …also code (as we both said) "somewhere else" to handle the parts lifetime (eg. wh/if FDataSource dies? wh/if FDataSource.DataSet dies/changes?) so we have a whole bunch of (almost) non-reusable code spreaded through the class in order to handle this.

    What I’m asking is:

    Can I have in TFooComp.SetDataSource(Value: TDataSource) something like TDataSource.DataSet.BeforePost add MyNewHandler?

    …and everything to be managed automatically, preferably /without/ having a TComponentMulticastEvent in each TFooComp instance. Imho, your answer, although quite ok, doesn’t address quite well this central (and single) point of my previous post.

    Just my2c & HTH,

    m. Th.

  5. m. Th. Says:

    Ouch, the blog engine ate (again) the angular braces. Rewritting the code:

    if Assigned(FDataSource) then
    if Assigned(FDataSource.FDataSet) then

  6. Nick Hodges » Blog Archive » Random Thoughts on the Passing Scene #82 Says:

    [...] When I was in Australia, I really enjoyed demoing the new language features in Delphi 2009.  Generics and Anonymous Methods are both really rich features that open up a whole new avenue of other things that you can do with the language.  Case in point:  Allen Bauer has a great series on using Generics to create a type-safe multi-case event system.  Catch up with Part 1, Part 2, and Part 3.  [...]

Leave a Comment


Server Response from: dnrh1.codegear.com

 
© Copyright 2008 Embarcadero Technologies, Inc. All Rights Reserved. Contact Us  |   Site Map  |   Legal Notices  |   Privacy Policy  |   Report Software Piracy