MEF composition issues, multithreading

I have the following code:

public class Temp<T, TMetadata> { [ImportMany] private IEnumerable<Lazy<T, TMetadata>> plugins; public Temp(string path) { AggregateCatalog aggregateCatalog = new AggregateCatalog(); aggregateCatalog.Catalogs.Add(new DirectoryCatalog(path)); CompositionContainer container = new CompositionContainer(aggregateCatalog); container.ComposeParts(this); } public T GetPlugin(Predicate<TMetadata> predicate) { Lazy<T, TMetadata> pluginInfo; try { pluginInfo = plugins.SingleOrDefault(p => predicate(p.Metadata)); } catch { // throw some exception } if (pluginInfo == null) { // throw some exception } return Clone(pluginInfo.Value); // -> this produces errors } } 

I have one Temp object and I call GetPlugin() from multiple threads. Sometimes I encounter unusual composition errors that I could not find a way to reproduce. For instance:

 "System.InvalidOperationException: Stack empty. at System.Collections.Generic.Stack`1.Pop() at System.ComponentModel.Composition.Hosting.ImportEngine.TrySatisfyImports(PartManager partManager, ComposablePart part, Boolean shouldTrackImports) at System.ComponentModel.Composition.Hosting.ImportEngine.SatisfyImports(ComposablePart part) at System.ComponentModel.Composition.Hosting.CompositionServices.GetExportedValueFromComposedPart(ImportEngine engine, ComposablePart part, ExportDefinition definition) at System.ComponentModel.Composition.Hosting.CatalogExportProvider.GetExportedValue(CatalogPart part, ExportDefinition export, Boolean isSharedPart) at System.ComponentModel.Composition.ExportServices.GetCastedExportedValue[T](Export export) at System.Lazy`1.CreateValue() at System.Lazy`1.LazyInitValue() at Temp`2.GetPlugin(Predicate`1 predicate)..." 

What could be the reason and how to cure this code?

+6
source share
2 answers

The CompositionContainer class has a little-known constructor that accepts the isThreadSafe parameter (the default value is false). If you create your container with this value equal to true, I believe that your problem will be solved:

 CompositionContainer container = new CompositionContainer(aggregateCatalog, true); 

On the side of the note, not related to the original question, instead of calling Clone() in the plugin, you can use the factory export instead - this way you do not need to implement your own cloning method, since MEF will create a new instance for you.

+17
source

If you want to get a list of available Export for the corresponding type of import, you do not need to use the (problematic) container.ComposeParts(this);

You can do something more:

 var pluginsAvailable = container.GetExports<T>().Select(y => y.Value).ToArray(); 

And this will give you an array of available instances, without any problems with threads that hit MEF.

I was working on something like this today ... please excuse me for giving the code:

 using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.IO; using System.Linq; using System.Reflection; using System.Threading; namespace PluginWatcher { /// <summary> /// Watch for changes to a plugin directory for a specific MEF Import type. /// <para>Keeps a list of last seen exports and exposes a change event</para> /// </summary> /// <typeparam name="T">Plugin type. Plugins should contain classes implementing this type and decorated with [Export(typeof(...))]</typeparam> public interface IPluginWatcher<T> : IDisposable { /// <summary> /// Available Exports matching type <typeparamref name="T"/> have changed /// </summary> event EventHandler<PluginsChangedEventArgs<T>> PluginsChanged; /// <summary> /// Last known Exports matching type <typeparamref name="T"/>. /// </summary> IEnumerable<T> CurrentlyAvailable { get; } } /// <summary> /// Event arguments relating to a change in available MEF Export types. /// </summary> public class PluginsChangedEventArgs<T>: EventArgs { /// <summary> /// Last known Exports matching type <typeparamref name="T"/>. /// </summary> public IEnumerable<T> AvailablePlugins { get; set; } } /// <summary> /// Watch for changes to a plugin directory for a specific MEF Import type. /// <para>Keeps a list of last seen exports and exposes a change event</para> /// </summary> /// <typeparam name="T">Plugin type. Plugins should contain classes implementing this type and decorated with [Export(typeof(...))]</typeparam> public class PluginWatcher<T> : IPluginWatcher<T> { private readonly object _compositionLock = new object(); private FileSystemWatcher _fsw; private DirectoryCatalog _pluginCatalog; private CompositionContainer _container; private AssemblyCatalog _localCatalog; private AggregateCatalog _catalog; public event EventHandler<PluginsChangedEventArgs<T>> PluginsChanged; protected virtual void OnPluginsChanged() { var handler = PluginsChanged; if (handler != null) handler(this, new PluginsChangedEventArgs<T> { AvailablePlugins = CurrentlyAvailable }); } public PluginWatcher(string pluginDirectory) { if (!Directory.Exists(pluginDirectory)) throw new Exception("Can't watch \"" + pluginDirectory + "\", might not exist or not enough permissions"); CurrentlyAvailable = new T[0]; _fsw = new FileSystemWatcher(pluginDirectory, "*.dll"); SetupFileWatcher(); try { _pluginCatalog = new DirectoryCatalog(pluginDirectory); _localCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); _catalog = new AggregateCatalog(); _catalog.Catalogs.Add(_localCatalog); _catalog.Catalogs.Add(_pluginCatalog); _container = new CompositionContainer(_catalog, false); _container.ExportsChanged += ExportsChanged; } catch { Dispose(true); throw; } ReadLoadedPlugins(); } private void SetupFileWatcher() { _fsw.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.FileName | NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.Security; _fsw.Changed += FileAddedOrRemoved; _fsw.Created += FileAddedOrRemoved; _fsw.Deleted += FileAddedOrRemoved; _fsw.Renamed += FileRenamed; _fsw.EnableRaisingEvents = true; } private void ExportsChanged(object sender, ExportsChangeEventArgs e) { lock (_compositionLock) { if (e.AddedExports.Any() || e.RemovedExports.Any()) ReadLoadedPlugins(); } } private void ReadLoadedPlugins() { CurrentlyAvailable = _container.GetExports<T>().Select(y => y.Value).ToArray(); OnPluginsChanged(); } private void FileRenamed(object sender, RenamedEventArgs e) { RefreshPlugins(); } void FileAddedOrRemoved(object sender, FileSystemEventArgs e) { RefreshPlugins(); } private void RefreshPlugins() { try { var cat = _pluginCatalog; if (cat == null) { return; } lock (_compositionLock) { cat.Refresh(); } } catch (ChangeRejectedException rejex) { Console.WriteLine("Could not update plugins: " + rejex.Message); } } public IEnumerable<T> CurrentlyAvailable { get; protected set; } ~PluginWatcher() { Dispose(true); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected void Dispose(bool disposing) { if (!disposing) return; var fsw = Interlocked.Exchange(ref _fsw, null); if (fsw != null) fsw.Dispose(); var plg = Interlocked.Exchange(ref _pluginCatalog, null); if (plg != null) plg.Dispose(); var con = Interlocked.Exchange(ref _container, null); if (con != null) con.Dispose(); var loc = Interlocked.Exchange(ref _localCatalog, null); if (loc != null) loc.Dispose(); var cat = Interlocked.Exchange(ref _catalog, null); if (cat != null) cat.Dispose(); } } } 
0
source

All Articles