2012-11-20 172 views
2

在.NET 4.0中使用MEF來節省我大量的抽象工廠代碼和配置gubbins。由於未部署,因此無法移至.net 4.5。MEF實例和多線程

/// <summary> 
/// Factory relies upon the use of the .net 4.0 MEF framework 
/// All processors need to export themselves to make themselves visible to the 'Processors' import property auto MEF population 
/// This class is implemented as a singleton 
/// </summary> 
public class MessageProsessorFactory 
{ 
    private static readonly string pluginFilenameFilter = "Connectors.*.dll"; 
    private static CompositionContainer _container; 
    private static MessageProsessorFactory _instance; 
    private static object MessageProsessorFactoryLock = new object(); 

    /// <summary> 
    /// Initializes the <see cref="MessageProsessorFactory" /> class. 
    /// Loads all MEF imports 
    /// </summary> 
    /// <exception cref="System.NotSupportedException"></exception> 
    private MessageProsessorFactory() 
    { 
     lock (MessageProsessorFactoryLock) 
     { 
      if (_container == null) 
      { 
       RemoveDllSecurityZoneRestrictions(); 

       //Create a thread safe composition container 
       _container = new CompositionContainer(new DirectoryCatalog(".", pluginFilenameFilter), true, null); 

       _container.ComposeParts(this); 
      } 
     } 
    } 

    /// <summary> 
    /// A list of detected class instances that support IMessageProcessor 
    /// </summary> 
    [ImportMany(typeof(IMessageProcessor), RequiredCreationPolicy = CreationPolicy.NonShared)] 
    private List<Lazy<IMessageProcessor, IMessageProccessorExportMetadata>> Processors { get; set; } 

    /// <summary> 
    /// Gets the message factory. 
    /// </summary> 
    /// <param name="messageEnvelope">The message envelope.</param> 
    /// <returns><see cref="IMessageProcessor"/></returns> 
    /// <exception cref="System.NotSupportedException">The supplied target is not supported: + target</exception> 
    public static IMessageProcessor GetMessageProcessor(MessageEnvelope messageEnvelope) 
    { 
     if (_instance == null) 
      _instance = new MessageProsessorFactory(); 

     var p = _instance.Processors.FirstOrDefault(
        s => s.Metadata.ExpectedType.AssemblyQualifiedName == messageEnvelope.AssemblyQualifiedName); 

     if (p == null) 
      throw new NotSupportedException(
       "The supplied type is not supported: " + messageEnvelope.AssemblyQualifiedName); 

     return p.Value; 

    } 

    /// <summary> 
    /// Removes any zone flags otherwise MEF wont load files with 
    /// a URL zone flag set to anything other than 'MyComputer', we are trusting all pluggins here. 
    /// http://msdn.microsoft.com/en-us/library/ms537183(v=vs.85).aspx 
    /// </summary> 
    private static void RemoveDllSecurityZoneRestrictions() 
    { 
     string path = System.IO.Path.GetDirectoryName(
          System.Reflection.Assembly.GetExecutingAssembly().Location); 

     foreach (var filePath in Directory.EnumerateFiles(path, pluginFilenameFilter)) 
     { 
      var zone = Zone.CreateFromUrl(filePath); 

      if (zone.SecurityZone != SecurityZone.MyComputer) 
      { 
       var fileInfo = new FileInfo(filePath); 
       fileInfo.DeleteAlternateDataStream("Zone.Identifier"); 
      } 
     } 
    } 
} 

_container.ComposeParts(this);之後被調用時,處理器會填充找到的所有IMessageProcessor實現。大。

注意

  • GetMessageProcessor是由許多線程調用。
  • 我們無法控制開發人員如何構造IMessageProcessor的實現類 ,因此我們無法保證 是線程安全的 - 可重入。但是,該類必須使用「導出」屬性進行檢測。

出口屬性

[MetadataAttribute] 
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] 
public class MessageProccessorExportAttribute : ExportAttribute 
{ 
    public MessageProccessorExportAttribute() 
     : base(typeof(IMessageProcessor)) 
    { 
    } 

    public Type ExpectedType { get; set; } 
} 
  • ExpectedType是什麼筆記 IMessageProcessor.ProcessMessage()預計將處理,純粹 實現細節只是元數據。

我的問題是,我到處讀到每個導入的實例都是Singleton,而不管它的激活策略是通過Lazy <>引用構造的。

因此,我們不能允許從GetMessageProcessor返回MEF中的實例,因爲多個線程將獲得相同的實例,這是不可取的。唉唉! 我想知道下面的「解決方法」是否是最好的方法,或者我有MEF堅持概念錯誤。

我的解決方法是將看似毫無意義的RequiredCreationPolicy = CreationPolicy.NonShared屬性設置更改爲CreationPolicy.Shared

然後改變函數GetMessageProcessor手動創建一個新的實例,真正從MEF中分離出來。使用MEF共享實例prulry作爲類型列表。

IMessageProcessor newInstance = (IMessageProcessor)Activator.CreateInstance(p.Value.GetType()); 

完整的方法;

public static IMessageProcessor GetMessageProcessor(MessageEnvelope messageEnvelope) 
    { 
     if (_instance == null) 
      _instance = new MessageProsessorFactory(); 

     var p = _instance.Processors.FirstOrDefault(
        s => s.Metadata.ExpectedType.AssemblyQualifiedName == messageEnvelope.AssemblyQualifiedName); 

     if (p == null) 
      throw new NotSupportedException(
       "The supplied type is not supported: " + messageEnvelope.AssemblyQualifiedName); 

     // we need to create a new instance from the singleton instance provided by MEF to assure we get a instance un-associated with the MEF container for 
     // currently as of .net 4.0 it wants to keep references on objects which may impact memory consumption. 
     // As we have no control over how a developer will organise there class that exposes an Export, 
     // this could lead to multithreading issues as an imported lazy instance is a singleton regardless 
     // of the RequiredCreationPolicy. 
     // MEF is still invaluable in avoided a tone of abstract factory code and its dynamic detection of all supporting 
     // Exports conforming to IMessageProcessor means there is no factory config for us to maintain. 

     IMessageProcessor newInstance = (IMessageProcessor)Activator.CreateInstance(p.Value.GetType()); 

     return newInstance; 



    } 
+0

不會這種方法失敗,如果一個'IMessageProcessor'實例都有一個'ImportingConstructor'而不是默認的構造函數?此外,它不會滿足新實例的任何導入。 –

回答

2

像這樣的東西應該工作:

public class MessageProsessorFactory 
{ 
    private const string pluginFilenameFilter = "Connectors.*.dll"; 
    private static readonly Lazy<CompositionContainer> _container 
     = new Lazy<CompositionContainer>(CreateContainer, true); 

    private static CompositionContainer CreateContainer() 
    { 
     RemoveDllSecurityZoneRestrictions(); 
     var catalog = new DirectoryCatalog(".", pluginFilenameFilter); 
     return new CompositionContainer(catalog, true, null); 
    } 

    public static IMessageProcessor GetMessageProcessor(MessageEnvelope messageEnvelope) 
    { 
     var processors = _container.Value.GetExports<IMessageProcessor, IMessageProccessorExportMetadata>(); 
     var p = processors.FirstOrDefault(s => s.Metadata.ExpectedType.AssemblyQualifiedName == messageEnvelope.AssemblyQualifiedName); 
     if (p == null) throw new NotSupportedException("The supplied type is not supported: " + messageEnvelope.AssemblyQualifiedName); 
     return p.Value; 
    } 

    private static void RemoveDllSecurityZoneRestrictions() 
    { 
     // As before. 
     // PS: Nice to see someone found a use for my code! :) 
     // http://www.codeproject.com/Articles/2670/Accessing-alternative-data-streams-of-files-on-an 
     ... 
    } 
} 
+0

我更喜歡使用Lazy <>中的另一個構造函數實例化容器的所有靜態方法。我認爲陪審團仍然在什麼容器上。價值。根據IMessageProcessor的新實例或MEF仍然維護的共享引用的方式,GetExport將返回,並且期望您通知它可以在完成時處理實例,從而導致潛在的資源泄漏,因爲沒有回來,它也只給了我們一個內部持有的同一個實例的引用,類似於一個單例。我讀過.Net 4.5 - – Terry

+0

@Terry:只要你的出口有'[PartCreationPolicy(CreationPolicy.NonShared)]',這個方法就會爲每次調用GetMessageProcessor返回一個導出類的新實例'。 –

+0

顯然不是,我剛剛發現這裏描述的同樣的問題:http://stackoverflow.com/questions/3203614/mef-lazy-importmany-with-creationpolicy-nonshared我認爲這仍然是一個問題,悲哀。我發現文檔沒有說明這一點。它目前仍然只是通過Activator.CreateInstance(part.getType)的方式實現這一目標,但顯然MEF很快就會提供這個功能.. http://blogs.msdn.com/b/nblumhardt/archive/2009/08 /28/dynamic-part-instantiation-in-mef.aspx – Terry