MEF组成问题,多线程

时间:2014-01-16 07:25:27

标签: c# .net multithreading thread-safety 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)..."

可能是什么原因以及如何解决此代码?

2 个答案:

答案 0 :(得分:19)

CompositionContainer类的little-known constructor接受isThreadSafe参数(出于性能原因,默认为false)。如果您将此值设置为true来创建容器,我相信您的问题将得到解决:

CompositionContainer container = new CompositionContainer(aggregateCatalog, true);

在旁注上,与原始问题无关,而不是在插件上调用Clone(),而是使用an export factory代替 - 这样您就不必实现自己的克隆方法了,因为MEF会为你创建一个新实例。

答案 1 :(得分: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();
        }
    }
}