{"id":137,"date":"2008-08-09T21:24:09","date_gmt":"2008-08-09T09:24:09","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=137"},"modified":"2009-08-07T14:16:28","modified_gmt":"2009-08-07T02:16:28","slug":"multicast-events-part-3","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/137\/","title":{"rendered":"Multicast Events &#8211; Part 3"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">4<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span><p>So far we&#8217;ve seen a multicast event implementation in (fairly limited) action, and dissected the core of it&#8217;s implementation, which was a fairly dry affair.<\/p>\n<p>I also demonstrated a flaw in the initial implementation &#8211; a susceptibility to objects adding handlers to events but not removing them when being destroyed.\u00a0 Before the .NET crowd get all smug, we should note that the relationship between an event source and it&#8217;s listeners is <a href=\"http:\/\/trevorunlocked.blogspot.com\/2007\/10\/c-advanced-event-handling-memory.html\">potentially problematic, in .NET also<\/a>.<\/p>\n<p>Fortunately I devised a solution to the problem in my framework.\u00a0 The solution &#8211; rather neatly &#8211; was itself provided by a multicast event.<!--more--><\/p>\n<p><a href=\"https:\/\/www.deltics.co.nz\/blog\/?p=147\">The video demonstrated the problem<\/a>, but it might be worth simplifying the problem and looking at it in terms of an <strong>Source<\/strong> object with some <strong>Event<\/strong>, and a <strong>Listener<\/strong> with some <strong>Handler<\/strong>.\u00a0 We can create the problem that we need to solve very easily:<\/p>\n<pre class=\"delphi\">  Source.Event.Add( Listener.Handler );\r\n  Listener.Free;\r\n  Source.Event.DoEvent;<\/pre>\n<p>Our problem is that when <strong>Listener<\/strong> is free&#8217;d the <strong>Source<\/strong> object is most likely oblivious to that fact, and the <strong>Event<\/strong> certainly is.\u00a0 We could simply require that every object that adds handlers to events be careful to remove them:<\/p>\n<pre class=\"delphi\">  Source.Event.Add( Listener.Handler );\r\n  Source.Event.Remove( Listener.Handler);\r\n  Listener.Free;\r\n  Source.Event.DoEvent;<\/pre>\n<p>An obvious and straightforward solution, but it will soon get tedious and if even one handler is missed and left &#8220;dangling&#8221; in some event somewhere the problem remains to all intents and purposes unsolved<\/p>\n<p>In a lot of observer\/observable implementations I have worked with (which is essentially the pattern we are dealing with after all) some form of &#8220;event manager&#8221; is used to co-ordinate the relationships between event sources and the listeners.\u00a0 This simplifies things to an extent &#8211; when implementing our listener we now need only remember to notify the event manager when we are destroyed, and it will clean up our event handlers for us after walking through all the relationships and finding those ones in which our listener is involved.<\/p>\n<p>But the event manager itself almost certainly places it&#8217;s own demands on an implementation, and in a system with a lot of event sources and\/or listeners however, the implementation of the event manager itself can become quite complex and a potential drag on performance.<\/p>\n<p>My solution is to provide a framework for easily establishing the necessary communication between event sources and listeners, and limiting that communication to only the sources and listeners directly involved in a relationship.<\/p>\n<p>The solution is not <em>completely<\/em> automatic, but the little work that is required in many cases is actually work that you would have found desirable to do &#8211; if not essential anyway.<\/p>\n<h3>Communication Is A Two Way Process<\/h3>\n<p>Any object that has handlers for multicast events is, by definition, participating in a multicast enabled framework.\u00a0 The most basic need in such a framework is for &#8220;object has been destroyed&#8221; type notifications, so it makes sense to have a multicast equivalent to an <strong>OnDestroy<\/strong> event.<\/p>\n<p>That is, in our condensed example our <strong>Listener<\/strong> is highly likely to implement an <strong>On_Destroy<\/strong> event of it&#8217;s own, which it will call in it&#8217;s destructor chain.<\/p>\n<p>Remember when we add a handler to a multicast event we are actually working with two pointers &#8211; a pointer to the handler method (<strong>Code<\/strong>) and a pointer to the instance of the class (<strong>Data<\/strong>) that provides that method.\u00a0 In other words the <strong>Data<\/strong> pointer is the <strong>Listener<\/strong> object.<\/p>\n<p>If the <strong>Listener<\/strong> object supports an <strong>On_Destroy<\/strong> event then the event it is listening to can add it&#8217;s own handler to the listener&#8217;s <strong>On_Destroy<\/strong> event and respond by removing any handlers implemented by that listener.<\/p>\n<p>But given an untyped pointer, how can we determine whether a given listener supports an <strong>On_Destroy<\/strong> event, and for that matter, get a reference to that event so that we can add a handler?<\/p>\n<h3>Staring Me Right In The Interface<\/h3>\n<p>The answer is of course an <strong>interface<\/strong>.\u00a0 An event handler must be a <strong>procedure <span style=\"text-decoration: underline;\">of object<\/span><\/strong>, so whatever that <strong>Data<\/strong> pointer points to it must be a <strong>TObject<\/strong> and given one of those, querying and obtaining interfaces is straightforward.<\/p>\n<p>So I define a standard interface <strong>IOn_Destroy<\/strong>:<\/p>\n<pre class=\"delphi\">\u00a0 IOn_Destroy = interface\r\n  ['{A3670CB7-683A-4C61-8B51-6531FA9559CA}']\r\n    function get_On_Destroy: TMultiCastNotify;\r\n    property On_Destroy: TMultiCastNotify read get_On_Destroy;\r\n  end;<\/pre>\n<p>Some changes were then made in <strong>TMultiCastEvent<\/strong> &#8211; notice that these changes are in the base class, so all multicast event classes benefit.<\/p>\n<p>First, the handler that will respond when a listener is destroyed &#8211; this is of course a <strong>TNotifyEvent<\/strong> handler since the <strong>On_Destroy<\/strong> event is a <strong>TMultiCastNotify<\/strong>:<\/p>\n<pre class=\"delphi\">  procedure TMultiCastEvent.ListenerDestroyed(aSender: TObject);\r\n  var\r\n    i: Integer;\r\n    method: PMethod;\r\n  begin\r\n    for i := 0 to Pred(Count) do\r\n    begin\r\n      method := fMethods[i];\r\n      if (method.Data = Pointer(aSender)) then\r\n      begin\r\n        Dispose(method);\r\n        fMethods[i] := NIL;\r\n      end;\r\n    end;\r\n\r\n    fMethods.Pack;\r\n  end;<\/pre>\n<p>This finds the handler methods implemented by the instance that is being destroyed and removes them.\u00a0 The second change is to identify listeners to the event that support <strong>IOn_Destroy<\/strong> and add this listener to those events, so we add the following to the Add() method of <strong>TMultiCastEvent<\/strong>:<\/p>\n<pre class=\"delphi\">    obj := TObject(aMethod.Data);\r\n    if Supports(obj, IOn_Destroy, listener) then\r\n      listener.On_Destroy.Add(ListenerDestroyed);<\/pre>\n<p>But wait.\u00a0 Surely all this does is reverse the problem?\u00a0 If the <strong>Source<\/strong> object is destroyed before the <strong>Listener<\/strong> it will destroy the <strong>Event<\/strong> and when the <strong>Listener<\/strong> fires it&#8217;s <strong>On_Destroy<\/strong> the <strong>Event<\/strong> will no longer be there!<\/p>\n<p>This is a problem, but we can solve that easily enough with a small change to the loop that disposes the method records in the destructor of the event itself:<\/p>\n<pre class=\"delphi\">    for i := 0 to Pred(fMethods.Count) do\r\n    begin\r\n      obj := TObject(PMethod(fMethods[i]).Data);\r\n\r\n      if Supports(obj, IOn_Destroy, listener) then\r\n        listener.On_Destroy.Remove(ListenerDestroyed);\r\n\r\n      Dispose(fMethods[i]);\r\n    end;<\/pre>\n<p>There remains one small gap in all this housekeeping but it is &#8211; so far as I can tell &#8211; harmless.  No points or prizes, but can you see what it is?<\/p>\n<h3>Recap<\/h3>\n<p>I have extended the initial, basic implementation to take care of some useful housekeeping for us &#8211; something that might even make your C#\/.NET friends a little jealous!<\/p>\n<p>\ud83d\ude42<\/p>\n<p>As last time, the test project containing these housekeeping changes is <a href=\"https:\/\/www.deltics.co.nz\/download\/multicast2.zip\">available for download<\/a> for you to play with.<\/p>\n<p>I&#8217;m not quite done yet.\u00a0 I have one more post to offer in which I will cover a number of further minor additions and refinements to the implementation that I have found to be useful.\u00a0 The fully documented and polished implementation &#8211; complete with those refinements &#8211; will accompany that post.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">4<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> So far we&#8217;ve seen a multicast event implementation in (fairly limited) action, and dissected the core of it&#8217;s implementation, which was a fairly dry affair. I also demonstrated a flaw in the initial implementation &#8211; a susceptibility to objects adding handlers to events but not removing them when being destroyed.\u00a0 Before the .NET crowd get [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[4],"tags":[31,292,28,29,33,25,32],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-2d","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":86,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/86\/","url_meta":{"origin":137,"position":0},"title":"Multicast Events &#8211; Part 1","date":"05 Aug 2008","format":false,"excerpt":"I remember one of the things that got me excited when I first read about the as-then new fangled C# and .NET stuff coming out of Microsoft was the idea that a single event could have multiple handlers. Cutting a long and irrelevant story short, my interest in .NET waned,\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":160,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/160\/","url_meta":{"origin":137,"position":1},"title":"MultiCast Events &#8211; Conclusion","date":"11 Aug 2008","format":false,"excerpt":"Drawing this subject to a close (finally!), here's the concluding post I promised, including the fully documented and finished implementation that has been serving me well for almost 2 years.\u00a0 The finished implementation incorporates a number of refinements to the core framework, and those are what we shall briefly look\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":147,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/147\/","url_meta":{"origin":137,"position":2},"title":"MultiCast Events &#8211; Video #2","date":"09 Aug 2008","format":false,"excerpt":"At the end of my last post I described a problem that arises if an object adds handlers to an event and is then destroyed without having removed those handlers.\u00a0 This video demonstrates that problem and also shows a solution provided by the multicast events framework itself. The details of\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":102,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/102\/","url_meta":{"origin":137,"position":3},"title":"Multicast Events &#8211; Part 2","date":"07 Aug 2008","format":false,"excerpt":"Having covered some of the basic use of multicast events, in this second post I shall start to build the implementation.\u00a0 In this first iteration we will provide the basics of a multicast event - managing and calling multiple handlers and the ability to enable and disable an event. The\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":180,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/180\/","url_meta":{"origin":137,"position":4},"title":"Latest Tibur\u00f3n Preview","date":"14 Aug 2008","format":false,"excerpt":"I was a little disappointed that the preview webinar this morning was little more than a re-run of the same content from a little over a week ago, albeit with some downloadable PowerPoint slides this time. It was at least an opportunity for some more Q&A and a couple of\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":3037,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/3037\/","url_meta":{"origin":137,"position":5},"title":"ReRaise Exception Bug in 10.4.1 x86 Compiler","date":"09 Dec 2020","format":false,"excerpt":"I just ran into a very frustrating issue in Delphi 10.4.1 with long-standing, basic functionality that is now broken in certain circumstances. Fortunately there is a work-around in those certain circumstances, but it's not pretty. The Problem: Re-Raising an exception causes an immediate Access Violation error at the raise statement\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/image.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/137"}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=137"}],"version-history":[{"count":11,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/137\/revisions"}],"predecessor-version":[{"id":470,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/137\/revisions\/470"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=137"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=137"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=137"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}