Lifetime of Services and Commands, or weird Service behavior
Hello,
I have a command that calls a method on an injected service and listens for the result event. I am observing that the first time the command is fired, the event handler is called once. the second time the command is executed, the event handler is called twice. the third time, its called three times. I am wondering about the lifetimes of commands and services? AFAIK commands are very short lived, and i'm assuming services are long lived so the same instance can be injected whenever necessary. I suspect that the framework creates a single eventDispatcher that it injects everywhere, so i tried removing the event listener before adding in case i was just adding additional event listeners. The Service just makes some REST calls and sends a single event upon success.
thanks in advance for any assistance!
g
here is the command code:
// Public Interface
//
[Inject]
public var event:PersistVideoEvent;
[Inject]
public var service:ICdnService;
[Inject]
public var model:ComposerModel;
// Overrides
//
override public function execute():void
{
var video:VideoAsset = model.currentVideo;
service.eventDispatcher.removeEventListener(VideoDetailsUpdatedEvent.VIDEO_DETAILS_UPDATED,onVideoDetailsUpdated);
service.eventDispatcher.addEventListener(VideoDetailsUpdatedEvent.VIDEO_DETAILS_UPDATED,onVideoDetailsUpdated);
service.updateVideoDetails(video.id,video.startDate,video.endDate,video.name,video.descriptions,video.actions);
}
// Event Handlers
//
protected function onVideoDetailsUpdated(event:VideoDetailsUpdatedEvent):void
{
if (event.video.id == model.currentVideo.id)
{
// success
Alert.show("Video has been successfully updated","Success",Alert.OK);
}
else
{
// fail
Alert.show("Video was not successfully updated","Fail",Alert.OK);
logger.error("Attempt to update video details failed");
}
}
Comments are currently closed for this discussion. You can start a new one.
Keyboard shortcuts
Generic
? | Show this help |
---|---|
ESC | Blurs the current field |
Comment Form
r | Focus the comment reply box |
---|---|
^ + ↩ | Submit the comment |
You can use Command ⌘
instead of Control ^
on Mac
1 Posted by krasimir on 04 Jan, 2011 07:23 PM
Hello,
I don't have great experience with robotlegs, but I think that the problem that you described occurs because you are mapping the service as a singleton. In this case when you inject your service you will access the same instance of that class. Or in other words you are adding the same listener several times, every time when the command is executed. So the question is how did you map your service into the context.
By the way, you can use hasEventListener instead of removeEventListener.
2 Posted by Gerry Koh on 04 Jan, 2011 07:30 PM
Yes, I'm mapping the service as a Singleton:
(btw, what does the Of do? I've just copied examples using it and it works)
I'm assuming that both the service and especially its eventDispatcher stick around for a while, which is why i try to remove existing event listeners first.
A thought just occurred to me: if the command itself is short lived, then adding an event listener from two instances of the command might result in two separate listeners on the eventDispatcher, which might result in the event being dispatched twice and both caught by the current command instance.
i am going to try removing the event listener in the event handler, when the command instance is still the same. maybe using weak references would help too. i'll report back, thanks!
g
3 Posted by Gerry Koh on 04 Jan, 2011 07:37 PM
that was totally it! i don't know the details of how eventDispatchers work, but i'm guessing that having two separate command instances added to its listeners array somehow caused it to fire two events on the same bus, which were received by the current command twice. removing the event listener in the event handler solved the problem, and i suspect weak references would too, but that might depend of garbage collection timing.
4 Posted by Gerry Koh on 04 Jan, 2011 07:38 PM
here is the fixed code:
Gerry Koh closed this discussion on 04 Jan, 2011 07:38 PM.
krasimir re-opened this discussion on 04 Jan, 2011 07:41 PM
5 Posted by krasimir on 04 Jan, 2011 07:41 PM
Can you please give more details about your service class. About ICdnService and OoyalaService. Are they extending something.
If you ask me, I'll add the listener for
VideoDetailsUpdatedEvent.VIDEO_DETAILS_UPDATED
in the body of the Service, not outside it. I don't know the structure of your application but for me it looks much much better if you place the listeners there and connect them with private functions.From the documentation:
mapSingleton provides a single instance of the requested class for every injection. Providing a single instance of a class across all injections ensures that you maintain a consistent state and don’t create unnecessary instances of the injected class. This is a managed single instance, enforced by the framework, and not a Singleton enforced within the class itself.
mapSingletonOf is much like mapSingleton in functionality. It is useful for mapping abstract classes and interfaces, where mapSingleton is for mapping concrete class implementations.
6 Posted by Paul Robertson on 04 Jan, 2011 07:47 PM
The "Of" in mapSingletonOf() differentiates between a singleton that is
mapped as an interface implementation versus a singleton that isn't
mapped to an interface.
The difference is in what data type you use for your [Inject] properties
-- if you want to use the concrete data type, use mapSingleton(), if you
want to specify an interface use mapSingletonOf().
(For services I would always use an interface, because it makes it easy
to swap them out for testing or if the implementation changes -- both of
which I've personally encountered.)
Back to your general question, I think the crux of the issue is that
Commands aren't meant to have state, including doing event handling.
(There are several interesting variations on Commands that do handle
events, but that's not the "standard" MVCS implementation.)
In a typical MVCS implementation, the command calls the service and
that's the end of the command. The service has its own event handlers
for its own events. When the service result returns, it dispatches an
event indicating success or failure, including the relevent data.
That way, any actions that should be triggered by the result/failure of
the service can be mapped to the event that the service dispatches. For
example, if you want to update a model based on the service call, you
can have a command mapped to the event that the service dispatches and
update the model from there. If you want to display some data or change
the view, you can have a mediator listen for that event and notify the
view. (Or the mediator can listen for a change event dispatched by the
model and use that to trigger the view change.)
I realize the Alert in the event handlers is probably just for testing,
but I would personally try to stay away from that since Alert is clearly
a view-related thing.
Paul
7 Posted by Gerry Koh on 04 Jan, 2011 08:07 PM
the service implements Actor, and ICdnService is just its interface. i use interfaces for services so that must be why i've always used mapSingletonOf. I don't need any listeners inside the service except for HTTPService listeners, and i don't want to pass those events on to the application for coupling reasons. I prefer to use events rather than callbacks to interface the service to the rest of the application, but am always open to better ways to do things!
here is the service call code. it is basically 4 or 5 REST calls chained together, with the final result handler dispatching the success event:
8 Posted by Gerry Koh on 04 Jan, 2011 08:11 PM
Paul,
I see your point about the lifespan of commands. I'm open to listening for service events directly in the appropriate mediator rather than the command itself. it seemed to me that given the choice to couple a command to a service or a view mediator to a service that the command would be the better choice, but i suppose a service interface change will require a change somewhere in the app anyway.
thanks!
9 Posted by Paul Robertson on 04 Jan, 2011 09:09 PM
Like you I don't usually like to dispatch service-specific events into
my app. Typically I do one of two things with my services:
1. Create a custom event containing the result data, fault info, etc.
and dispatch an instance of that from the service. That way I'm not
tying my app to the specific event used in the service unless I really
like it for some reason.
2. Inject my model into the service, and use the service result to set a
value on the model directly. Setting the value on the model then
dispatches a change event that is picked up by any interested parts of
the code such as mediators. This couples my service to my model, so some
people (myself included at times) would discourage this. This is where
you get into judgment calls about "appropriate" coupling.
Paul
10 Posted by Abel de Beer on 04 Jan, 2011 10:39 PM
Switch to AS3Signals! It makes your code much more elegant.
11 Posted by Gerry Koh on 05 Jan, 2011 12:34 AM
Abel, I have been trying to switch to Signals :) I downloaded the core project and another one that provided a SignalContext or something, got errors, and got no responses to my posts on here.
You could help me greatly by pointing me to the repositories of the projects needed to add signals support into my application, and please make a note of which versions work for you, when I had played with it the head revisions in the master branch did not work for me.
12 Posted by Gerry Koh on 05 Jan, 2011 12:34 AM
thanks paul,
those are good options.
13 Posted by Nikos on 06 Jan, 2011 01:16 PM
I fail to see the point of this since the command will be discarded
// Event Handlers
14 Posted by Gerry Koh on 06 Jan, 2011 05:26 PM
nikos, when you execute a command, insert a breakpoint and inspect eventDispatcher. it has an array of listeners. since commands are short lived, each command has a different object id and occupies a different spot in the listeners array.
i suspect that by not removing the listener in the same command instance that created it results in extra entries in eventDispatcher.listeners.
i don't know enough about the specifics or the robotlegs event system to know why that resulted in my event handler being called once for each extra entry in the listeners array, but when i moved the removeEventListener call to the same command instance that registered it, the problem went away.
15 Posted by Nikos on 06 Jan, 2011 05:35 PM
see this thread, maybe you have extra mediators?
http://knowledge.robotlegs.org/discussions/questions/134-how-do-i-k...
16 Posted by Abel de Beer on 06 Jan, 2011 07:20 PM
@Gerry: I use the following versions of the core libraries / extensions in my latest Signals enabled project:
Robotlegs v1.4.0 / AS3Signals v0.8 / Joel Hooks' Signals extension for Robotlegs
You should add the Robotlegs and AS3Signals SWC files to your classpath and add the files inside the extension's 'src' folder to your own 'src' folder. To be able to access the signalCommandMap, your Context should extend SignalContext. Let me know if you can get it to work.
Stray closed this discussion on 11 Feb, 2011 11:04 PM.