2014-01-16 13 views
6

I einen folgenden Code haben:MEF Zusammensetzung Ausgaben, Multithreading

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 
    } 
} 

Ich habe ein einzelnes Objekt von Temp und ich rufe GetPlugin() von mehreren Threads. Manchmal habe ich seltsame Kompositionsfehler, die ich nicht reproduzieren konnte. Zum Beispiel:

"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)..." 

Was könnte ein Grund sein und wie diesen Code zu heilen?

+0

Haben Sie versucht, die 'Lock' Anweisung im Block Versuchen Sie es mit? – LueTm

+0

@LueTm Nein, ich kann das Problem nicht so oft reproduzieren, also möchte ich die Idee verstehen, was los ist und warum ... Ich kann nicht einfach versuchen zu sehen, was passieren wird –

+0

Ich vermute Race Condition. – LueTm

Antwort

17

Die Klasse CompositionContainer hat eine little-known constructor, die einen isThreadSafe-Parameter akzeptiert (der aus Leistungsgründen standardmäßig auf "false" gesetzt ist). Wenn Sie Ihren Container mit diesem Wert auf true gesetzt schaffen werden, glaube ich, Ihr Problem gelöst werden:

CompositionContainer container = new CompositionContainer(aggregateCatalog, true); 

Auf einer Seite zur Kenntnis, in keinem Zusammenhang mit der ursprünglichen Frage, statt Clone() auf dem Plugin aufrufen, können Sie Verwenden Sie stattdessen an export factory - auf diese Weise müssen Sie keine eigene Klonmethode implementieren, da MEF eine neue Instanz für Sie erstellt.

+1

Upvote für den Link zu 'ExportFactory' :-), aber das isThreadSafe-Flag auf CompositionContainer tut so gut wie nichts, um die Probleme mit' ComposeContainer' zu lösen (zumindest soweit ich es erlebt habe - ich wäre froh, es zu sein erwies sich als falsch!) –

+0

@IainBallard Sieht so aus, als hätte es mindestens einer Person geholfen. Hier ist [der Beweis] (http://blogs.microsoft.co.il/zuker/2011/01/02/mef-thread-safety-and-getexportedvalue/#comment-2450456) wie angefordert;) –

+0

@AdiLester Yup. Danke für die ExportFactory! Es ist das erste Mal, dass ich MEF benutze. Aber ich bin mir noch nicht sicher, dass das Problem weg ist ... brauche etwas Zeit, weil ich keinen Komponententest erfinden kann, um das Problem zu reproduzieren. Ich habe versucht, GetPlugin() von 1000 laufenden Aufgaben aufzurufen, aber auch in meiner schlechten Version war alles cool. –

0

Wenn Sie eine Liste der verfügbaren Exporte für eine passende Import-Typ erhalten möchten, brauchen Sie nicht die (problematisch) verwenden container.ComposeParts(this);

Sie tun können, etwas mehr wie:

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

Und das wird Ihnen eine Reihe von verfügbaren Instanzen geben, ohne all die Threading-Probleme, die MEF plagen.

Ich habe auf so etwas wie diese heute arbeiten ... bitte den Code Dump entschuldigen:

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(); 
     } 
    } 
}