理解MEF容器中部件的生命周期及其含義是非常重要的。鑒于MEF重點在開放端應用程序,這將變得尤其重要的,一旦app ships和第三方擴展開始運行,作為應用程序的開發者將很好地控制這一系列的部件。生命周期可以被解釋為這樣一個部件期望的共享物,無論是一個新的部件被創建還是一個部件被關閉或釋放都由控制策略來翻譯。
Shared, Non Shared and ownership
通過使用PartCreationPolicyAttribute特性設置CreationPolicy(類級別)來定義一個部件的共享物。下面的值是受支持的:
Shared:部件作者告訴MEF,一個部件的實例可以存在在每一個容器中(指定將由容器創建關聯的該ComposablePart 的單個共享實例,并由所有請求者共享該實例)
NonShared:部件作者告訴MEF,一個部件每一次的導出請求都將由一個部件新的實例來提供服務。(指定將由容器為每個請求者創建一個關聯的該ComposablePart的新的非共享實例)
Any or not supplied value:部件作者允許部件既可以支持Shared,也可以支持NonShared.
可以使用[System.ComponentModel.Composition.PartCreationPolicyAttribute]特性在一個部件上定義創建策略:
[PartCreationPolicy(CreationPolicy.NonShared)] [Export(typeof(IMessageSender))] public class SmtpSender : IMessageSender { }
這個容器將一直擁有它創建的部件的所有權。換句話說,該所有權從不會轉移到一個通過使用容器實例(直接地)或一個導入(間接地)來請求它的行動者上來。
導入也可以定義或者約束這種被用來提供導入值的部件策略創建。你需要做的一切是為RequiredCreationPolicy指定CreationPolicy枚舉值:
[Export] public class Importer {[Import(RequiredCreationPolicy=CreationPolicy.NonShared)]public Dependency Dep { get; set; } }
對于與importer相關的部件需要使用共享的場景來說是很用的。默認地,RequiredCreationPolicy被設置為Any,因此Shared或者NonShared部件都可以提供值。
|
?
?
?
?
?注意:當兩邊都定義CreationPolicy為Any,結果它將是一個Shared部件。
來個例子:
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks;namespace PartLifetime {class Program{[ImportMany(RequiredCreationPolicy=CreationPolicy.Shared)]public IEnumerable<IMessageSender> Senders { get; set; }static void Main(string[] args){Program p = new Program();p.Compose();foreach (var item in p.Senders){item.Send("Hi,MEF");}Console.ReadKey();}void Compose(){AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());var container = new CompositionContainer(catalog);container.ComposeParts(this);}}interface IMessageSender{void Send(string msg);}[Export(typeof(IMessageSender))][PartCreationPolicy(CreationPolicy.Shared)]class EmailSender : IMessageSender{public void Send(string msg){Console.WriteLine("Email sent:" + msg);}}[Export(typeof(IMessageSender))][PartCreationPolicy(CreationPolicy.NonShared)]class SMSSender : IMessageSender{public void Send(string msg){Console.WriteLine("SMS sent:" + msg);}}}
我們發現,當Import的請求創建策略為Shared,則自動匹配EmailSender組件;當為NonShared時,自動匹配SMSSende組件;當省略或者為Any時,自動匹配Shared和NonShared。
釋放容器
通常,一個容器實例是部件生命周期的持有者。由容器創建的部件實例擁有的生命周期取決于容器的生命周期。標志容器生命周期結束的方式是釋放它。釋放一個容器的含義是:
- 實現IDisposable接口的部件將會調用Dispose方法
- 被容器占有的部件的引用將被清除
- Shared組件將被釋放和清除
- Lazy導出組件在容器被釋放后將不會工作
- 操作可能會拋出System.ObjectDisposedExecption
容器和部件引用
我們相信,.Net GC是適當地清理可依賴的最好的東西。然后,我們也需要提供一個有確定性行為的容器。因此,這個容器將不會擁有它創建的部件的引用,除非下面的條件之一成立:
- 這個部件被標記為Shared
- 這個部件實現了IDisposable接口
- 一個或者多個部件被配置為允許重組
對于那些條件,部件引用將被容器擁有。結合這個事實,你可以有NonShared部件,并且一直從容器來請求它們,然后內存需求將迅速成為一個問題。為了減輕這個問題,你應該依靠在下面接下來的兩個話題的討論的策略。
作用域操作和資源提前回收
一些常見類型的應用程序,像web apps和windows服務,在每個桌面應用上卻又很大不同。它們可能更加依賴批量簡短的操作。例如,一個windows服務可能會直接地監視,一旦一批可預估的文件存在,就將開始一個批處理操作來轉換這些文件成另外一種格式。Web操作可能由每次請求操作所決定。
對于那些場景,你應該使用子容器或者提前釋放對象。后者可以使容器釋放和清掉非共享的部件。
為了提前釋放對象,你需要調用由組合容器暴露的ReleaseExport方法。
var batchProcessorExport = container.GetExport<IBatchProcessor>();var batchProcessor = batchProcessorExport.Value; batchProcessor.Process();container.ReleaseExport(batchProcessorExport);
容器分層
另一種解決相同問題的方式是使用容器分層。你可以創建容器并將它連接到一個父容器并作為其子容器。注意除非你提供了一個不同的catalog到子容器中,否則將不會有很大幫助,因為仍然會在父容器中實例化。
因此,你應該做的是,基于一種標準過濾父容器,這種標準是應該被創建在父容器中的一系列部件和那些應該被創建在子容器中的部件區分開來,或者是,你應該完全指定一個新的catalog來暴露一系列應該被創建在子容器中的部件。子容器正如所期望的那樣是短期存在的,創建在它里面的部件將會更早地被釋放掉。一個通用的解決辦法是將共享的部件創建在父容器中,而將非共享的部件創建在子容器中。由于共享部件可能會依賴由非共享部件提供的導出,這時主catalog必須包含整個一系列的部件而子容器應該有一個僅包含非共享部件的過濾主容器的視圖。
可處理命令
可處理命令并不能以任何方式確保。那意味著你不應該在你的dispose方法上試圖使用導入。例如:
[Export] public class SomeService : IDisposable {[Import]public ILogger Logger { get; set; }public void Dispose(){Logger.Info("Disposing"); // might throw exception! } }
在你的dispose方法實現上使用導入的logger實例可能會有問題,因為這個ILogger契約的實現也可能是可處理的,而此時可能它已經被處理掉了。
添加部件/移除部件
并不是每一個部件都是由容器創建的。你也可以從容器中添加和移除部件。這個過程觸發了容器,
使其開始創建部件來滿足遞歸添加的部件的依賴。當部件被移除時,MEF足夠聰明,它將會回收資源并且處理掉被部件添加的非共享部件。
注意:MEF將從不會占有你提供的實例的所有權,但是,它有由它自己創建的滿足你實例導入的部件的所有權。
using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.ComponentModel.Composition.Primitives; using System.Linq; using System.Text; using System.Threading.Tasks;namespace PartLifetime {class Example{static void Main(){var catalog = new AssemblyCatalog(typeof(Program).Assembly);var container = new CompositionContainer(catalog);var root = new Root();//add external part container.ComposeParts(root);//...use the composed root instance//remove external part//var batch = new CompositionBatch();//var rootPart = batch.AddExportedValue<Root>(new Root());//container.Compose(batch);//batch = new CompositionBatch();//batch.RemovePart(rootPart);//container.Compose(batch);container.ReleaseExport<NonSharedDependency>(new Lazy<NonSharedDependency>());Console.ReadKey();}}class Root{[Import(RequiredCreationPolicy=CreationPolicy.NonShared)]public NonSharedDependency Dep { get; set; }}[Export,PartCreationPolicy(CreationPolicy.NonShared)]class NonSharedDependency : IDisposable{public void Dispose(){Console.WriteLine("Disposed");}}}
?