OnDemand Loading

Topics: Prism v2 - Silverlight 2
Apr 14, 2009 at 10:15 PM
I am using on demand loading to load a module that has services not view composition. For example:

modManager.LoadModule("EMailService");
IEmailService emailer = boot.Container.Resolve<IEmailService>();

The problem is that the LoadModule isn't synchronous so the resolution fails (this code is called before the Initialize of the module is called to register the type). Is there any way to do this? I want to load the module and wait on the loading of the module so I can resolve a type.  Right now its hit and miss...I could enter a wait state but I don't have anything to wait on.  

Ideas?
Apr 15, 2009 at 8:10 PM

Hi,

 

You are absolutely right, in Silverlight prism applications modules that are loaded on demand are downloaded asynchronously. Unfortunately the ModuleLoader class does not provide any event to signal that a module has been loaded. Moreover the ModuleLoader class has no virtual methods that be can overridden to add this functionality. Implementing a custom IModuleManager and IModuleTypeLoader could be another solution. A more straightforward approach would be coding it in the modules as follows:

 

I believe the best way to keep modules decoupled is using the EventAggregator to publish and subscribe to an event when the module is loaded. So you should do 2 things: Publish a ModuleLoadedEvent when the module is loaded and subscribe it to execute code that depends on that module. One way of doing this could be:

1.       Define a ModuleLoadedEvent as explained in the How to: Create and Publish Events.

2.       Publish a ModuleLoadedEvent in the Initialize method of your EMailServiceModule:

public void Initialize()

{

    container.RegisterInstance<IEmailService>(new EmailService());

    ModuleLoadedEvent moduleLoadedEvent = eventAggregator.GetEvent<ModuleLoadedEvent>();

    moduleLoadedEvent.Publish("EMailService");

}

3.       Handle to the ModuleLoadedEvent

 

this.moduleManager.LoadModule("EMailService");

ModuleLoadedEvent moduleLoadedEvent = eventAggregator.GetEvent<ModuleLoadedEvent>();

moduleLoadedEvent.Subscribe(ModuleLoadedEventHandler, ThreadOption.PublisherThread, false, FilterModule);

4.       Define the Handlers of the event. I have used a Filter to get only the event of the specific module but that is optional.

public bool FilterModule(String moduleName)

{

return moduleName.Equals("EMailService");

}

 

public void ModuleLoadedEventHandler(String moduleName)

{

var service = this.container.Resolve<IMyService>();

// ...

}

 

                You can find a detailed explanation on how to use the EventAggragator in the How to: Create and Publish Events and How to: Subscribe and Unsubscribe to Events.

 

Hope it helps!

 

Matias Bonaventura

http://blogs.southworks.net/matiasb

Apr 15, 2009 at 8:19 PM
Thanks, that makes sense...but....

I'd really like to push for a new feature in the next drop to address this. This seems like a common strategy and to have to build all this pumping isn't very friendly.  I'd be happy to submit a patch.
Apr 16, 2009 at 11:49 AM
Hi,

I raised an issue about this a while ago [url:3427|http://compositewpf.codeplex.com/WorkItem/View.aspx?WorkItemId=3427]

I overcame this by altering the ModuleManager.LoadModule method to take a callback.
This seems to work with the limited testing I've done  - I haven't tested modules with dependencies either...


ie

{
        private readonly object _lockCallbacks = new object();
        private readonly Dictionary<string, Action> _callbacks = new Dictionary<string, Action>();

    public void LoadModule(string moduleName, Action callback)
        {
            IEnumerable<ModuleInfo> module = this.moduleCatalog.Modules.Where(m => m.ModuleName == moduleName);
            if (module == null || module.Count() != 1)
            {
                throw new ModuleNotFoundException(moduleName, string.Format(CultureInfo.CurrentCulture, Resources.ModuleNotFound, moduleName));
            }

            
                //if module is already loaded just invoke callback
                //otherwise add callback to dictionary to invoke once
                //module is initiallized
            if (module.First().State == ModuleState.Initialized)
            {
                if (callback != null)
                {
                    callback.Invoke();
                }
            }
            else
            {
                lock (_lockCallbacks)
                {
                    _callbacks.Add(moduleName, callback);
                }
            }

            IEnumerable<ModuleInfo> modulesToLoad = this.moduleCatalog.CompleteListWithDependencies(module);
            this.LoadModuleTypes(modulesToLoad);
        }
}

and altering LoadModulesThatAreReadyForLoad to invoke the callback once loaded

{
    private void LoadModulesThatAreReadyForLoad()
        {
            bool keepLoading = true;
            while (keepLoading)
            {
                keepLoading = false;
                IEnumerable<ModuleInfo> availableModules = this.moduleCatalog.Modules.Where(m => m.State == ModuleState.ReadyForInitialization);

                foreach (ModuleInfo moduleInfo in availableModules)
                {
                    if (this.AreDependenciesLoaded(moduleInfo))
                    {
                        moduleInfo.State = ModuleState.Initializing;
                        this.InitializeModule(moduleInfo);
                        keepLoading = true;

                        lock (_lockCallbacks)
                        {
                            Action callback;
                            var callbackName = moduleInfo.ModuleName;
                            _callbacks.TryGetValue(callbackName, out callback);
                            if(callback!=null)
                            {
                                callback.Invoke();
                                _callbacks.Remove(callbackName);
                            }
                        }

                        break;
                    }
                }
            }
        }
}
Jul 28, 2009 at 10:56 AM

It seems the main problem is how to block the method which calls LoadModule until the module has completed initialisation.  Whether we use the event aggregator or callbacks, there needs to be something blocking the UI thread preventing the call to Container.Resolve until this event/callback occurs.

Mark

Jul 29, 2009 at 12:23 PM

Hi Mark,

Since asych operations are more "queued" then mutli-threaded (everything runs on the UI thread) I found this to be a complex issue for a requirement where I had to have multiple files finish uploading before operations on them started; if I put the thread to sleep - the entire application slept.   My work-around was to call a web service (providing the number of files expected) and it contained the loop (where I could put the thread to sleep) that would check for the existence all files before returning.  The web service's callback method was responsible for initiating/resuming the file processing.   Note: the delay was insignificant in regards to timing out but critical to being there before processing could resume.

One work-around, would be interested in hearing others...