function or method is a piece of code that is provided from outside a component to provide information needed for the component's job.
Typical example is the Java Comparator that must be provided to the BinaryTree
When the BinaryTree
calls the compare
method on the Comparator instance, it wants the result now, not later. The completion of the CallBack?
is needed before further processing can be done. Therefore, CallBack?
s are synchronous.
An event function or method is a piece of code that is provided from outside a component that should be called by this component when something specific happens.
Typical example is any ModelViewController
When the event condition occurs, the component typically scans through all registered event listeners to call the appropriate method or function. If this call is performed synchronously, several problems can occur:
- If the listener throws an exception, the error must be handled by the component, while the problem has typically nothing to do with its internal logic
- The listener can unregister itself from the list, leading to the concurrent collection modification problem (the listener list gets modified while it is being iterated).
Therefore, Events are asynchronous: you should always dispatch events through an event queue and not process then synchronously. Events should be processed by EventQueue
, or by some GuiThread
This pattern leads to the OutdatedEvent
Events can be implemented as call-backs, regardless of merit of that implimenation, such that the title is misleading I believe. It should be "Don't use callbacks for events" or the like.
Events don't have to be async. When nothing else is going on, a single-threaded system can process events. If something is going on, either events can be ignored, or queued up until the time that processing returns to the event handler. I am not necessarily claiming this is the best way, but it does not require async. --top
Async does not mean multi-threaded. Async means that events are queued up somewhere and dispatched at a later time. Everything can be in the same thread. Most of the time, it actually is.
Either way, I don't see "call backs" and events as being mutually exclusive. One may pass an event ID/name/object as a "call back" for example. Specifying which event is triggered when a button is pressed, for example, is using both call-backs and events. --top
That's a way of seing things. The key point here is that you should not dispatch events synchronously. Beside that, that's true that an event handler is nothing but a callback method from the EventQueue perspective.
I think Phillipe provided an overly-broad definition of 'CallBack?
' procedure. A CallBack?
procedure is a signalling mechanism combined with the processing on that signal; it is used to "Call" back. True function-passing does not constitute any sort of signalling mechanism; when you pass in an evaluator-function (like the Java comparator) that function is not intended for use in signalling. Really, it's just another sort of value. Function-values are not callback functions.
Well that's a question of vocabulary then. Depending on the context, you will call them signal handlers (IRQ) or CallBack? functions (Windows WndProc?). The key point is that they must terminate before the underlying process can resume what it was doing before firing the event/signal. Whether or not the CallBack? returns an actual value (in term of function return type) is not that relevant. The CallBack? function could be a void Procedure that reprograms the hardware, flushes a buffer, commits a transaction or whathever, but it has to be done NOW. An event doesn't have this constraint. -- PhilippeDetournay
I'd think to the contrary. An identifier or description of a callback procedure is usually something passed about precisely because it doesn't need to be done now
but, rather, at some point in the future. Whether there are other ordering or timing constraints is another issue. I certainly cannot see where tying the definition of a callback procedure to a set of ordering constraints by the performer of the callback is beneficial. Ordering constraints in communications are part of the 'protocol' level of abstraction, not of mechanism.
I agree that 'events are not callbacks', but my reasoning is different. 'Events' encompass far more than the execution of callback procedures; every communication-event is an event, whether or not it utilizes a procedure or some datum passed in to 'call back'. Communication itself is the fundamental
event performed by any computation system... be it a brain, a CPU, or a RAM subsystem.
Once again, I don't want to start a discussion about definitions, the terms we're using here are not clearly defined anyway (see WhatIsAnEvent). The purpose of this page is GUI-related, and my point is that GUI events, or any events in the scope of EventDrivenProgramming should be processed asynchronously, and not synchronously. Events are not the operations (I call then CallBack?s here) of a VisitorPattern. -- PhilippeDetournay
I can see where we're bumping into language issues. However, I'd still argue that there is no inherent reason for the operations of a VisitorPattern
to be synchronous, either. It's convenient to program it that way, but the visitor pattern does not (inherently) require it. Ordering constraints on visitor patterns (e.g. 'draw that which is in the background first') are separate from the concept of visitor patterns. I can think of many situations where asynchronous visitor-pattern operations would be entirely sensible on a machine that can effectively handle it. Outwards communications are included (e.g. drawing to a screen).
During evaluation, use of functions and folds provide a sort of evaluative visitor-pattern... e.g. one can describe a new list as being the mapping of some function to an old list, or functional flattening of a tree. But even these don't inherently introduce ordering. I.e. a 'map' function (map function list) needs to return a new list with a certain list-ordering constraint such that if 'eN' is the nth element of 'list', then '(function eN)' is the nth element of the return value... but there is no imposition upon evaluation order, or whether this needs to be 'synchronous' or not.
Synchronous vs. Asynchronous should be held ONLY for protocol-level, and should not be considered relevant in the context of mechanism (like callback). If it is essential that items in the list are 'visited' and 'executed' in order, that's a protocol constraint. When working with largely imperative languages, dealing with constraining protocol-crap is often required for even the lowest-level of programming; e.g. "you mustn't request the state of a memory unit before that memory unit has been initialized". Synchronization is /always, always, always/ about communications.
Callbacks are also about communications. However, there is no inherent requirement that the actor doing a 'callback' (in general) do so in any synchronous manner; if the callback is performed in accordance with all contracts and other protocol constraints, its use is provably correct regardless of whether its use is synchronous relative to other actions. Saying that the every callback must be
synchronous artificially adds to the set of protocol constraints, and I cannot think of anything you gain from it.
Where do you believe you are gaining, formally or otherwise, from the notion that callbacks must be synchronous?
I'm not gaining anything from defining CallBack?s as synchronous. I'm gaining a lot, however, from stating that GUI events must be asynchronous. The remaining is purely illustrative, and my examples try to use situations we all know, seen and implemented. They are not there to define a new theory about what a CallBack? is, and whether such a CallBack? must be synchronous or not according to that theory. What I know is that 99% of the CallBack? mechanisms I ever seen, as well as 99% of the Visitor implementations ever written use synchronous mechanisms, and therefore they offer good illustrative materials. But my point is not that the remaining 1% is not allowed to exist. Once again, this page is not about defining what a CallBack? or an Event is. --PhilippeDetournay
Every synchronous set of events is also an asynchronous set of events, so I'm not sure what stating "GUI events must be asynchronous
" actually means. I'm not confident that it has any meaning. Perhaps "GUI events may be asynchronous
" states it better? Or, possibly, "any well-designed user-agent ought be able to capture and process new user-input events even whilst processing older ones
" and "any well-designed user-agent ought be able to determine whether new user-requests interact with, cancel, undo, or are superseded by those activities still in progress
". Seeing as a GUI is just one very primitive form of user-agent...
In any case, GUI events are hardly the only sort, and whether such events ought be processed in parallel or in series is entirely based on the domain. Also, the title seems waaay off if this page is not about defining callbacks or events, or even USING definitions of such. After all, to make any claim that 'EventsAreNotCallBacks
' requires that you first have a definition for 'event' and for 'callback'.
That callbacks and visitor implementations often use synchronous mechanisms is largely a result of systems running fast enough on domain data-sets that going multi-threaded is very rarely useful as an optimization technique... especially when using visitor patterns for communications; most network and video-cards don't get any faster if you slam them with multiple streams of data on a multi-processor machine... in fact, many would become utterly confused. Moving to multiple threads can't be used for optimization in any
case on a single-processor machine. My earlier quote of "entirely sensible on a machine that can effectively handle it" is, in fact, a rather significant condition. However, if I were performing a visitor-based search on an unfold-value (unfolds are potentially infinite) or an inference-engine or planning system, going asynchronous can be a very useful optimization if I have a machine with many processors. I just need the right domain and the right machine to make it worthwhile.
Where on Earth are you going with your parallelism and multithreading? I said asynchronous, I did not say parallel. Asynchronous means
not now, it does not mean
do it now but keep doing something else meanwhile. Asynchronous event processing is at the core of any GUI toolkit, including Windows GDI, JavaAwt, JavaSwt and so on. They do that on purpose, for very good reasons that I'm trying to document on this page. Some of these toolkits are SingleThreaded? (GDI, SWT), some are MultiThreaded? (AWT), but they all implement some EventQueue, containing what I call events. Not knowing why will make you head to the classical naive implementation of listeners using lists or sets and direct call to the listener instance. I call these callbacks, and this doesn't work. That's because EventsAreNotCallBacks. Now since it seems you're not interested about GUI programming, we can discuss about the particularities of inference-engine or planning systems, or about optimizations that can be performed using thread on a MultiCore? CPU. All these topics may have their very own definition or common usage for the terms event and callback. But this is not at all related to what I'm talking about in this page. -- PhilippeDetournay
Asynchronous does not
mean "not now". It means "not synchronous", which (in this context) essentially means "not constrained to start or finish or progress or otherwise occur at a time or rate relative to certain other events". I certainly agree that it doesn't mean "parallel"; hell, there are tons of "synchronous" events that MUST occur in parallel... like synchronized swimming, or synchronized audio-visual signals.
One thing to note is that user input-events damn well ARE asynchronous. This is not to say they occur "not now". This is to say that they are "not constrained to start or finish or progress or otherwise occur at a time or rate relative to certain other events"... including relative to the processing of the prior user input-events. This is WHY "any well-designed user-agent ought be able to capture and process new user-input events even whilst processing older ones
" and "any well-designed user-agent ought be able to determine whether new user-requests interact with, cancel, undo, or are superseded by those activities still in progress
". And that goes for GUIs, too.
GUI toolkits that offer no recourse for parallel activity (threads, continuations, or otherwise) are sensible only for systems where the processing of any given user-input event is so damn fast that it cannot be effectively interrupted by the user. Thus "whether such events ought be processed in parallel or in series is entirely based on the domain
". If you do execute GUI events in a single-threaded queue based upon when you received them, however, you ARE processing them synchronously; you have just constrained their processing to occur in a specific order relative to the processing of other events. It seems your main argument here is not actually about whether synchronization should occur in the processing of these events, but rather about whether complex behavior-descriptions (which you are unfortunately calling 'callbacks') should be scheduled
rather than executed
by what is effectively an interrupt.
Anyhow, your title for this page is not apt for the tasking you're requiring of it. Perhaps a page dedicated to BehaviorOnGuiInterrupt?
would be appropriate, with accompanying pattern/anti-pattern pages ScheduleActionOnGuiInterrupt?
. As it is, I consider your effort to argue that EventsAreNotCallBacks
via reference to whatever you mean by 'GUI' events and 'GUI' callbacks to be somewhat off-topic. As it is, EventsAreNotCallBacks
simply by virtue of the fact that any reasonable notion of 'event' (including naive, English, answers from WhatIsAnEvent
, etc.) will encompass far more than 'calling back'.
There is a slightly better question as to whether your "EventFunctionsAreNotCallBackFunctions?
You started this topic by providing a definition for CallBack?
functions ("A CallBack?
function or method is a piece of code that is provided from outside a component to provide information needed for the component's job"). You then define an 'event function' (but not 'event') ("An event function or method is a piece of code that is provided from outside a component that should be called by this component when something specific happens.")... which I'd really call a "hook" or an "observer callback", not an "event function". Now, it seems you're arguing that the callback must "provide information" or otherwise be central to the performance of the component's job whereas an "event function" does not provide information and its removal would not compromise the correctness of the component.
I agree that these two are definitely disparate uses for higher-order-procedures and higher-order-functions.
I disagree with your hasty generalization involving a comparator for a binary tree that (implicitly all
s are synchronous. There are plenty of situations where a callback procedure is a necessary piece of a software component's behavior and for which execution of that procedure ought be parallel.
I do agree with the no-big-things-on-interrupt policy in general, and that goes for GUI interrupts, too. If you must inform a bunch of users about an event ASAP, I'd reject interrupt-time use of these "event functions" (aka 'hooks' or 'listeners') unless I can make time-complexity guarantees.
However, I reject your notion that events-functions should be 'asynchronous' (by which you mean 'scheduled for later') simply because of the few problems you labeled. WRGT exceptions: listeners ought /never/ throw exceptions (since listeners aren't necessarily even on the same damn machine); if they do, catch them, log or ignore them, then move on. WRGT the iterator-issue: always iterate over a logical copy of the list. Problem solved. If you're scheduling them for later, you'd be copying nodes from your list into the event-queue anyway, so the cost is the same.
If I were to give a reason as to why these "event functions" ought not be called based on GUI-component inputs, it would be thus: Human-Inputs to a computer (via HCI-tools of any sort) ought be directed to an intelligent User-Agent whose job it is to interpret what the human-user intended by his or her actions and to present the human-user with sufficient information to make intelligent and correct decisions. As such, a truly intelligent user-agent must be capable of sensor-fusion, atop which it may provide simple or complex inference (ranging from 'button B means do X' to observing facial expression, recognizing moods, and listening to spoken natural language). The modern approach of having signals go to individual 'GUI components' rather than to one centralized 'user-agent' is a perversion of this inherently better model. It would have been easy enough to just create the equivalent of the current system by simply implementing a signal-routing-table within a very dumb, centralized user-agent. Making modern GUI toolkits work this conceptually correct
way (as primitive, idiotic User-Agents rather than widgets-with-attached-actions) requires retooling the GUI entirely... which is difficult, given the code-momentum already in place.
The equivalent of your registered 'GUI event functions' should be part of the user-agent behavior system... in particular, reaction-based behavior (or 'reflexive' behavior) whereby certain observed signals result automatically in certain actions. With modern approaches to GUI, you're stuck with user-agents with all the intelligence of a jellyfish. With the correct approach, you could largely get rid of reflexive behavior and move towards reliance on intelligent interpretation of the human.
I'm tired of talking semantics when I try to be practical, so I won't keep discussing on this topic. Check LaynesLaw for further details.
Oh, you don't need semantics. The words mean whatever you want them to mean, right? Your entire argument can be summarized as 'foobar' if you so deem it. :Sigh: It is true that I regard your use of words to be rather amateur (and suggest you bother grabbing a dictionary), and it is your poor use of words is what makes this topic -severely- off the declared title.
However, much of the arguments I have presented above utilize your
definitions of 'callback', 'asynchronous', and 'event'. And the reason I have an argument with you even under those definitions is because, frankly, your 'advice', though ill-worded, is good for reasons other than those you offered, and the reasons you offered are mistaken. You cannot dismiss those portions of my argument as being even remotely related to LaynesLaw
or semantics. Yet you, foolishly, are focusing on the one little thing you don't like: that I think your diction and selection of words is awful and insist on correcting you on it. Throwing a fit over that does not make the whole of my argument any weaker. Even using your definitions, even noting that your whole point is about GUI interrupts and delivery of messages to 'listeners', there are some massive, gaping holes in your original statement that you ought to address... or at least attempt to understand.
If you are done with this topic, I'll summarize both your intended advice and the fallacy behind it at the top. However, I feel confident that your interest in this subject would rise again shortly thereafter... so it would be better for us to come to an understanding down here.
Okay so let's try to be very concrete. In an EventBased?
piece of code:
Sounds simple. ObserverPattern
101. Now the question is: should the listeners be called before the setValue function returns? Does that make any difference? Let's take the following code:
All of a sudden, we can see that if listeners are notified of the change before the fire method returns, we have a problem: they might try to get the value and see that it is not (yet) updated. But of course, you would never do such an error because you would soon understand that firing events should be the very last things that must be done. Fair enough.
Now, let's have some fun. I have a subclass that wants to override the setValue method. It overrides it because it wants to do additional processing based on the new value.
But if the doCleverThings method must act on the new value, it'd better be called after the super.setValue call itself. So:
Problem is now that the listeners will be called before the doCleverThings method is called. Therefore, listeners might try to access an inconsistent instance.
All these are very trivial examples, but I'm sure you'll be able to find a lot of subtle refinements. What is the root cause of this problem? Stating that listeners should be called before the fire method returns will allow arbitrary code to be run within the current thread while your instance might be in a inconsistent state. In other words, you need to code your class in a fully reentrant way. And it turns out to be very difficult. Remember the listener removal story? This is a reetrency problem. You can find dedicated solutions for all of these problems separately (for instance by copying the listener list, I used to do that and it indeed worked for a while), but you'd better get rid of the problem completely.
If, however, change notifications are queued somewhere and processed only when the current notification processing returns, none of these problems can occur. And all of a sudden, it really doesn't matter whether you store the newValue before or after calling the fire method. This leads to other interresting problems (see OutdatedEvent
), but in my experience, the advantages highly outweight them.
This has other advantages too. For instance, the stack depth is always more or less the same, and does not depend on the event chaining that lead to the current situation. While it can be somehow more difficult to debug, exceptions will all fall within the same catch statement (centralized exception handling), and the exception stack trace is way more readable. Also, you don't have to be afraid about listener's exceptions (I remember having had some good time trying to debug intempestive InterruptedException
because some listeners were doing funny things...).
About threading, things can be a lot easier as well: it doesn't matter whether an event has been posted by one specific thread or by another. Once it is posted, it's gone, and the processing will be the same.
foreach(listener in listenerList)
should be replaced by
foreach(listener in listenerList)
Now it happens that I call the first one a CallBack?
and the second one an Event. Feel free to substitute any terminology you find more adequate, I don't really care. What I know is that my code works way better using the second approach rather than the first one. Call it a falacy if you want, but at least that's a fallacy that works.
Depending on your favorite language or GUI ToolKit?
, the dispatch method can be called PostMessage?
(Windows GDI), PostThreadMessage?
(Windows), invokeLater (AWT), asyncExec (SWT) or whatever.
That was much better! As I said, I agree your advice is good... just not the reasons you provided for it originally. Fallacy is always in the argument, Philippe, not the conclusion.
However, there are some points that should be addressed. On the 'consistency' argument (listeners might try to access an inconsistent instance
), the single cell set by the setValue() method is, in fact, consistent. What you are discussing, then, is consistency across other cell values that are affected by doCleverThings(). That is something that cannot be done correctly without some sort of transaction support that allows multiple-cell updates, which would prevent any update notifications from being fired prior to a consistent state being wholly committed. If you choose to provide that level of consistent-state transaction support by always delaying update notifications to a single point in the thread, then so be it; however, one ought be aware of the precise abstraction one is providing by the proposed mechanism... for there are many other means by which to gain provable consistency across many variables. I, personally, prefer the use of SharedMemoryTransactions?
(SMT), which provide transaction support for even a single thread. Of course, to use it in a single thread, you need to have continuations or fibers.
The greater problem is that of conceptual actors... there should be no 'arbitrary' code being run by a thread except at points where you wish 'arbitrary' code to run, and that may only be done by either constraining the code (a tasking for which most languages lack the proper typing facilities) or by moving arbitrary 'listener' code to run elsewhere. I'd argue that the 'correct' way to handle it is to constrain the code... that the 'listeners' are conceptual actors and must treat a value-update message just as they would any other incoming message on any other port, which means dropping it into their local message queue to be handled when they get around to it, unless they can prove a particular set of protocol requirements about their use of the message (including proof of guaranteed termination. If you prove a certain set of protocol requirements, you can also prove that certain 'immediate' communications are 'safe'. It's the communications version of tail recursion.)
As far as exceptions go, I stand by what I stated earlier: 'listeners' are conceptually separate actors, and may reasonably be located on different machines. The 'observer' pattern is a communications pattern, and the use of the callback (or 'event method') is just a mechanism to 'send' a message. As such, the only
sort of exceptions a 'listener' should ever
release back to you are communications
exceptions (e.g. link down, link disrupted, link detached, etc.). Communications exceptions must be handled via communications mechanisms (closing the link and releasing all resources, or saving the message in case of re-establishment for disruption tolerance, or attempting to re-send an earlier message (in case of a naq), etc. If any non-communications exception arrives through a communications link (like the 'listener' link provided by use of callback), it's simply a programming error. You simply cannot justify non-communications exceptions crossing communications boundaries... EVER.
You mention event queues in a multi-threaded system. I certainly don't have a problem with that: once you post an event, it's gone. Fine. Great, even; I love massively parallel, distributed programming... it was my focus for two years of schooling. But do be aware that you have now violated your earlier claim of consistency
... I.e. you just dropped a message notification to the EventQueue
in setValue(), and now listeners will be capturing many different inconsistent states as you continue onwards to doCleverThings(). This brings you right back to a requirement for supporting transactions or atomic operations of some other sort (e.g. via locking) if you wish to have consistency.
About the listener's exceptions: while I agree that a listener should never, ever throw an exception, you can't expect any BugFree? software. Therefore, you should always be ready for SegmentationViolation?s (that will be converted to so-called AsynchronousException?s on Windows), or some NullPointerException. When (not if) this will occur, having a centralized exception handling mechanism will allow you to have, at least, a consistent bug report mechanism. Or, at best, a centralized recovery procedure.
About the multithreading issue of consistency, I would say: well, yes and no. I have one and only one EventThread?, and most of the code runs from within this thread. I also have plenty of worker threads running around, but they all use event dispatching to send messages to the EventThread?. Therefore, event functions never have to bother about threading issues. In that sense, using an EventQueue instead of direct calls eases multithreading. The sender of the event might, indeed, be concerned about being called back from another thread, but that's not something expected to happen: a worker thread is not supposed to be called from the EventThread? except in exceptional circumstances that should be documented explicitely.
Of course, having one single EventThread? is not the best solution to take advantage of a lot of CPUs, but again, I'm on the GUI here.
Question: how would you use Fibers to solve the consistency problem of direct listener call?
Having only one EventThread?
does not avoid the issue of representing a consistent state to those listening to events. The basic issue there is that worker threads start dispatching messages whilst only half-done (i.e. they're still in the process of doCleverThings() when some other thread may receive an update notification). Thus, if consistent state is an issue at all, you can no longer guarantee it will be seen.
If listeners throw exceptions that they shouldn't, the easy thing to do is catch them then log or report them (or drop a logging/report request into the event queue...). I certainly agree that, when dealing with multi-party code, you should certainly be ready for segmentation violations and such... wherever a code-proof cannot be performed. This handling itself need not be performed centrally so long as it is written OnceAndOnlyOnce
. E.g. one could utilize a macro or class that automatically wraps listener-callbacks in the proper exception-handling code prior to executing them.
The main reason, IMO, to use an EventThread?
instead of delivering messages to listeners in their own threads is that you cannot, in the common languages used, perform any guarantees on the time-constraints for delivery of messages. If it were O(N) for N listeners and you can guarantee N is some small number, then firing the messages from the local thread wouldn't be a problem. If it were O(N) for N listeners and N is intended to scale to internet sizes, then you really need to develop an Akamai-style network for message broadcast. If it is not bound by big-O notation because you cannot guarantee the time a listener-callback (event-function) takes, and especially if you cannot guarantee that the listener-callback does not, itself, result directly or indirectly in other outgoing messages, you absolutely must not deliver it from the worker-thread because doing so prevents you from performing any code-proofs on the time constraints of that worker thread, which are often necessary to proof of correctness in time-sensitive programs.
Related to having no guarantees on time-constraints, I tend to run two event-queue related threads. One of them runs events, and the other fires off once every second or so, watching the event-queue to ensure that the other threads are healthy and that the queue emptied at a reasonable rate. If not, it fires off another EventThread?
themselves expire after so much time, which prevents multi-thread flooding due to any sort of sticky locking situation. This I do because most of my callbacks come from third-party DLLs about which I cannot make any guarantees whatsoever. This also directly results in parallel exception handling because I have many threads firing off events and, thus, many threads handling exceptions that should never exist in the first place... which is, perhaps, why that notion doesn't bother me even a little bit.
With regards to fibers and consistency, my statement was thus: "I, personally, prefer the use of SharedMemoryTransactions? (SMT), which provide transaction support for even a single thread. Of course, to use it in a single thread, you need to have continuations or fibers."
. The reason I mention continuations or fibers relates to the following situation. Imagine you have a single thread running your whole program, and you have event-dispatching as a consequence of updating variables. Some of these events, themselves, update variables... which results in further event-dispatching; it could even be the case that a variable is updated as a consequence of an event whilst still in the process of delivering its event-update message (which is why I stated that being aware of communications paths is necessary to safely optimize 'listeners' to actually run in the same thread as the current actor/worker). Now suppose you add SMT to the mix. The main thread starts a transaction (T0) and begins updating variables. Ideally, no messages are delivered as a consequence of updating these variables until they receive the 'commit' message (one should note that cells and memory can and probably ought to be treated as services). Let's assume this is the case; the individual SMT-Cells themselves will wait for the commit message /then/ deliver any notices to observers. However, the notion of delivering an 'atomic' commit message is not wholly possible; in particular, not every cell can receive the commit message at the same time. Thus, when you commit the first cell, it begins delivery of messages, which cause event-callbacks to update variables. If they attempt to update any of those still in the ready-to-commit stage, you must be able to back off... which requires that you be able to return from a continuation or yield control of a fiber. Much of that can be fixed for single-threading, I suppose, by simply adding an extra stage after commit to actually deliver messages (which will be processed wholly within the local thread) from the local thread /after/ all variables have committed. Still, it's quite a bit uglier than doing the same from multiple threads.
That seems a little thick... Essentially, SMT in an Observer environment must create a per-transaction EventQueue
of messages to deliver upon successful commit. This avoids the consisteny problems you mentioned earlier (because 'doCleverThings()' can occur in the current transaction) but does not avoid the issues that arise from lacking timing and communications constraints on the listener-callbacks. By use of this sort of per-transaction, in-thread EventQueue
, I suppose I can recant on the requirement for fibers or continuations; the need for those arises mostly from the cells themselves being the component that delivers messages upon commit.