【问题标题】:MEF composition issues, multithreadingMEF 组合问题,多线程
【发布时间】:2014-01-16 07:25:27
【问题描述】:

我有以下代码:

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

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

【问题讨论】:

  • 您是否尝试过在 try 块中使用 lock 语句?
  • @LueTm 不,我不能经常重现这个问题,所以我想了解发生了什么以及为什么......我不能只是尝试看看会发生什么跨度>
  • 我猜是比赛条件。
  • @LueTm 我正在考虑这个问题,因为错误是不同的、随机的并且发生在 MEF 内部......不是线程安全的对象通常会表现出这种行为
  • 好吧,您(可能)多次迭代序列,这通常不是一个好兆头,您(可能)每个项目多次调用谓词,这可能是一个问题。我们需要知道lazy's 的IEnumerable 实际代表什么,谓词在做什么,但最重要的是,我们需要了解lazy's 是如何创建的,因为这就是您的根本问题的根源,正在评估他们的价值。不知道它们来自哪里,我们不可能知道它们有什么问题。

标签: c# .net multithreading thread-safety mef


【解决方案1】:

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

CompositionContainer container = new CompositionContainer(aggregateCatalog, true);

附带说明,与原始问题无关,您可以使用 an export factory 而不是在插件上调用 Clone() - 这样您就不必实现自己的克隆方法,因为 MEF 将创建给你一个新的实例。

【讨论】:

  • 支持ExportFactory 的链接 :-),但是 CompositionContainer 上的 isThreadSafe 标志几乎没有帮助解决ComposeContainer 的问题(至少就我所经历的而言——我很高兴被证明是错误的!)
  • @IainBallard 看起来它至少帮助了一个人。这里是the proof 要求的;)
  • @AdiLester 是的。感谢出口工厂!这是我第一次使用 MEF。但我还不确定问题是否已经消失......需要一些时间,因为我无法发明一个单元测试来重现这个问题。我试图从 1000 个正在运行的任务中调用 GetPlugin(),但即使在我糟糕的版本中,一切都很酷。
  • @IainBallard 如果你想从多个线程访问它,你需要在线程安全模式下创建CompositionContainer。 MEF 组件内部有很多可变状态。在线程安全模式下,它会在这些点上开启同步。我自己观察到,如果 Web 服务器上没有此标志,MEF 可能会在 30 个并行线程中运行 1000 个请求而没有问题,但随后出现问题,随后的 1000 个请求使所有线程都失败。打开线程安全模式解决了这个问题。它应该这样做:)
【解决方案2】:

如果您想获取匹配导入类型的可用导出列表,则无需使用(有问题的)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();
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-10
    • 1970-01-01
    • 1970-01-01
    • 2013-06-02
    相关资源
    最近更新 更多