2014-01-16 45 views
6

我有以下代碼:MEF組合物的問題,多線程

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

我有Temp單個對象和我請從GetPlugin()多個線程。有時候我會面對奇怪的構圖錯誤,而我沒有找到重現的方式。例如:

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

什麼可能是一個原因和如何治癒這個代碼?

+0

您是否嘗試過使用'lock'聲明try塊? – LueTm

+0

@LueTm Nope,我不能經常重現這個問題,所以我想掌握正在發生的事情以及爲什麼......我不能只是試着看看會發生什麼 –

+0

我在猜測競爭狀態。 – LueTm

回答

17

CompositionContainer類有一個little-known constructor它接受isThreadSafe參數(由於性能原因,默認爲false)。如果你將這個值設置爲true創建的容器,我相信你的問題將得到解決:

CompositionContainer container = new CompositionContainer(aggregateCatalog, true); 

在一個側面說明,無關原來的問題,而不是在插件調用Clone(),你可以改爲使用an export factory - 這樣您就不必實現自己的克隆方法,因爲MEF將爲您創建一個新實例。

+1

Upvote鏈接到'ExportFactory' :-),但CompositionContainer上的isThreadSafe標誌幾乎沒有任何幫助解決「ComposeContainer」的問題(至少據我所知 - 我很樂意成爲證明是錯誤的!) –

+0

@IainBallard看起來它至少幫助了一個人。這裏是[證明](http://blogs.microsoft.co.il/zuker/2011/01/02/mef-thread-safety-and-getexportedvalue/#comment-2450456)根據要求;) –

+0

@AdiLester Yup。感謝ExportFactory!這是我第一次使用MEF。但我還不確定,問題已經消失......需要一些時間,因爲我無法發明單元測試來重現問題。我試圖從1000個運行任務中調用GetPlugin(),但即使在我可憐的版本中,一切都很酷。 –

0

如果你想獲得匹配的導入類型的可用出口的列表,你不需要使用(有問題的)container.ComposeParts(this);

你可以做更多的東西一樣:

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

這將爲您提供一系列可用的實例,而不存在困擾MEF的所有線程問題。

我一直在努力的東西今天這個樣子......請原諒的代碼轉儲:

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