4

由于我的应用程序架构发生了变化,我最近出现了循环依赖。

该应用程序依赖于通过 MEF 加载插件的插件管理器。直到一切正常,因为它看起来像这样:

// model.cs
[Export("Model")]
public class Model
{
  public PluginManager PM { get; set; }

  [ImportingConstructor]
  public Model( [Import] PluginManager plugin_manager)
  {
    PM = plugin_manager;
  }
}

// pluginmanager.cs
[Export(typeof(PluginManager))]
public class PluginManager
{
  [ImportMany(typeof(PluginInterface))]
  private IEnumerable<PluginInterface> Plugins { get; set; }
}

插件看起来像这样:

// myplugin.cs
[Export(typeof(PluginInterface))]
public class MyPlugin : PluginInterface
{
}

但是现在我遇到了一种情况,我希望所有插件都能够通过接口查询 PluginManager(或可能的任何其他对象),以了解系统中的其他插件以了解它们的功能。我通过添加另一个接口“解决”了这个问题,我们称之为 PluginQueryInterface。然后我让模型实现了这个接口。

[Export("Model"))]
[Export(typeof(PluginQueryInterface))]
public class Model : PluginQueryInterface
{
  // same as before
}

然后插件签名将如下所示:

// 1st possible implementation
[Export(typeof(PluginInterface))]
public class MyPlugin : PluginInterface
{
  [Import(typeof(PluginQueryInterface))]
  public PluginQueryInterface QueryInterface { get; set; }

  public MyPlugin() {}
}

或这个

// 2nd possible implementation
[Export(typeof(PluginInterface))]
public class MyPlugin : PluginInterface
{
  private  PluginQueryInterface QueryInterface { get; set; }

  [ImportingConstructor]
  public MyPlugin( [Import] PluginQueryInterface query_interface)
  {
    QueryInterface = query_interface
  }
}

第二实现很明显是一个循环引用,因为插件要求在创建插件之前创建 PluginQueryInterface,但是 PluginQueryInterface 是模型,它必须导入 PluginManager,而这又需要创建所有的 PluginInterfaces...我在启动时确实收到了 MEF 循环依赖错误。

一个实现似乎不是对我的循环引用。如果 PluginQueryInterface 是一个属性,那么我认为它在使用之前不会被解析。构造函数根本不使用它。那么为什么 PluginManager 不能愉快地创建我所有的 MyPlugins 呢?在这两种情况下,我都会得到相同的 MEF 错误。

我试图通过让 PluginManager 实现 PluginQueryInterface 来解决这个问题,因为 a) 无论如何它都是有意义的,并且 b) 它是处理循环依赖的一种已知方法——使两个相互依赖的类改为依赖于第三个类。现在的问题是我得到了一个不同的 MEF 错误!这就是它所说的:

GetExportedValue cannot be called before prerequisite import 'Company.App.PluginManager..ctor(Parameter="database_filepath", ContractName="PluginManager.filename")' has been set.

怎么回事?我在我的代码中设置了断点,并且在调用 GetExportedValue 之前已经设置PluginManager.filename 了我的导出值。

我完全被难住了。任何意见或建议现在将不胜感激。几个小时以来,我一直在用我的头撞在 MEF 包覆的墙上,试图调试这个问题。

(更新)

我之前没有考虑到这一点,但它可能是插件之间的差异,所以我删除了两个插件之一,现在我的应用程序加载时没有 MEF 错误。我把它加回去了,它又失败了。然后我删除了另一个插件,它起作用了。所以看起来这是其他一些 MEF 错误。就好像它不希望我加载多个具有特定界面的插件......但我正在使用 ImportMany,这不会表现出CardinalityException某种形式吗?

更新

我不了解 MEF 的这一部分,希望这里有人可以解释它的全部内容。进入代码一段时间后,我发现我的错误源于 MEF 在找到值后删除了导入定义!

    private bool TryGetImportValue(ImportDefinition definition, out object value)
    {
        lock (this._lock)
        {
            if (this._importValues.TryGetValue(definition, out value))
            {
                this._importValues.Remove(definition); // this is the line that got me
                return true;
            }
        }

        value = null;
        return false;
    }

我以前从未遇到过这个问题,坦率地说,我很难理解我现在正在做什么,我的进口和出口使这个问题浮出水面。我假设我正在做 MEF 设计者不打算让任何人做的事情。我可以盲目地注释掉this._importValues.Remove(definition);,但这不可能是正确的。我的猜测是,这将归结为我使用的 MEF 属性,但是由于导入此值的插件的创建策略为CreationPolicy.Shared,我为什么会有问题?

4

2 回答 2

3

此外,这可能是线程问题。您应该尝试将容器构造为 ThreadSafe:

http://blogs.microsoft.co.il/blogs/zuker/archive/2011/01/02/mef-thread-safety-and-getexportedvalue.aspx

于 2011-09-16T00:21:21.280 回答
3

好吧,我有一个可能的解决方案。我没有任何使用它的经验,但使用Lazy实例化似乎有帮助。至少我可以继续前进,而不必更改我不完全理解的 MEF 代码。

于 2010-09-13T13:27:40.250 回答