gpt4 book ai didi

c# - MEF 插件架构的即发即弃方法

转载 作者:太空狗 更新时间:2023-10-29 17:56:15 25 4
gpt4 key购买 nike

这个问题可能与设计相关或与代码相关,但我卡住了,所以我愿意接受任何类型的答案;正确方法的指针!

我已经使用 MEF(托管可扩展性框架)开发了一款 WPF 软件,该软件将充当插件的一种编排器形式。该应用程序只是根据用户的选择在插件之间重定向数据,因此根本不知道插件的作用(特别是因为它们可以由 3rd 方开发人员开发)。应用程序和插件共享一个接口(interface),以此了解调用哪些方法,因此流量是双向的:插件调用主应用程序中的方法向其发送数据,主应用程序将此数据传递给另一个插件.

到目前为止,这是有效的,但我在同步行为方面遇到了问题。接口(interface)定义的所有方法都缺少返回值(Void),我正在努力获得一种“即发即弃”的方法,在这种方法中,调用应用程序不需要等待插件接收函数完成执行代码(并返回到主应用程序的调用!)。

那么解决这个问题的最佳方法是什么?让每个插件(和主应用程序)将其工作负载放在某种“堆栈”上,以便能够将控制权返回给调用方,然后有一些单独运行的机制逐项通过堆栈工作(和这种堆叠方法是异步的吗?)?

其他值得注意的事情是,插件在单独的线程中运行(根据调试器线程窗口),当它们被初始化时,它们从调用主应用程序获得引用,因此它们可以在主应用程序中触发函数。插件还经常需要告诉主应用程序它们处于什么状态(空闲、工作、错误等),并且还发送数据以供主应用程序记录,因此这通常会创建一个嵌套的调用层次结构(如果你跟着我,很难解释)。

我为此使用 .Net 4.5。

下面是一些简化的代码示例。我替换了一些名字,所以如果某处有拼写错误,它就在这里,而不是在真正的代码中。 :)

界面:

public interface IMyPluggableApp 
{
void PluginStatus(string PluginInstanceGuid, PluginInstanceState PluginInstanceState);
void DataReceiver(string PluginInstanceGuid, string ConnectorGuid, object Data);
void Logg(string PluginInstanceGuid, LoggMessageType MessageType, string Message);
}

public interface IPluginExport
{
PluginInfo PluginInfo { get; set; }
void Initialize(string PluginInstanceGuid, Dictionary<string, string> PluginUserSettings, IMyPluggableApp MyPluggableApp);
void Start(string PluginInstanceGuid, List<ConnectorInstanceInfo> ConnectedOutputs);
void Stop(string PluginInstanceGuid);
void PluginClick(string PluginInstanceGuid);
void PlugginTrigger(string ConnectorGuid, object Data);
}

插件:

    public static IMyPluggableApp _MyPluggableApp

[PartCreationPolicy(CreationPolicy.NonShared)]
[Export(typeof(IPluginExport))]
public class PluginExport : IPluginExport
{
public void Initialize(string PluginInstanceGuid, Dictionary<string, string> pluginUserSettings, IMyPluggableApp refMyPluggableApp)
{
_MyPluggableApp = refMyPluggableApp; // Populate global object with a ref to the calling application

// some code for setting saved user preferences

_MyPluggableApp.PluginStatus(PluginInfo.PluginInstanceGuid, PluginInstanceState.Initialized); // Tell main app we're initialized
}
public void Start(string PluginInstanceGuid, List<ConnectorInstanceInfo> ConnectedOutputs)
{
// Some code for preparing the plugin functionality

_MyPluggableApp.PluginStatus(PluginInfo.PluginInstanceGuid, PluginInstanceState.Initialized); // Tell main app we started
}
public void PlugginTrigger(string ConnectorGuid, object Data)
{
_MyPluggableApp.PluginStatus(AvailablePlugins.PluginInfo.PluginInstanceGuid, PluginInstanceState.Running_Busy); // Tell main app we're busy

// Run the code that actually provides the functionality of this plugin

_MyPluggableApp.PluginStatus(AvailablePlugins.PluginInfo.PluginInstanceGuid, PluginInstanceState.Running_Idle); // Tell main app we're idle
}

// and so on ...
}

以及主要应用:

public partial class MainWindow : IMyPluggableApp 
{
[ImportMany(typeof(IPluginExport))]
IPluginExport[] _availablePlugins;

public void PluginStatus(string PluginInstanceGuid, PluginInstanceState PluginInstanceState)
{
// Code for setting status in GUI
}

public void DataReceiver(string PluginInstanceGuid, string ConnectorGuid, object Data)
{
ConnectorInfo connector_source = GetConnectorInfo(ConnectorGuid);
PluginInfo plugin_source = GetPluginInfo_ByPluginInstanceGuid(PluginInstanceGuid);

ConnectorInstanceInfo connector_destination = (from i in _project.PluginInstances
from y in i.ConnectedConnectors
where i.PluginInstanceGuid == PluginInstanceGuid
&& y.ConnectedFromOutput_ConnectorGuid == ConnectorGuid
select y).FirstOrDefault();

_availablePlugins.Where(xx => xx.PluginInfo.PluginInstanceGuid == connector_destination.ConnectedToInput_PluginInstanceGuid).First().PlugginTrigger(ConnectorGuid, Data);
}

public void Logg(string PluginInstanceGuid, LoggMessageType MessageType, string Message)
{
// Logg stuff
}
}

它是主应用程序中的 DataReceiver 函数,它接收数据,查看哪个插件应该拥有它,然后发送它(通过 PlugginTrigger 函数)。

最佳答案

一些观察:

  • 即发即弃是宿主 的要求,因此插件实现不必担心。
  • 我认为(如果我错了请纠正我)CLR 不支持在同一个 AppDomain 中以“即发即弃”的方式调用方法。如果您的插件被加载到单独的进程中,并且您使用 WCF 与它们通信,那么您可以简单地设置 IsOneWay property在你的 OperationContractAttribute 上。

第二点提出了一种解决方案,这对您的情况来说似乎有点矫枉过正 - 但无论如何我们还是要提一下。您的插件可以托管进程内 WCF 服务,并且 WPF 应用程序和插件之间的所有通信都可以通过 WCF 服务代理完成。然而,这伴随着配置噩梦,并且确实为您必须解决的一大堆其他问题打开了一堆蠕虫。

让我们从初始问题的一个简单示例开始,并尝试从那里解决它。这是带有插件的控制台应用程序的代码:

public class Program
{
private static void Main(string[] args)
{
var host = new CompositionHost();
new CompositionContainer(new AssemblyCatalog(typeof(Plugin).Assembly)).ComposeParts(host);
var plugin = host.Plugin;
plugin.Method();
Console.ReadLine();

}

private class CompositionHost: IPartImportsSatisfiedNotification
{
[Import(typeof (IPlugin))] private IPlugin _plugin;

public IPlugin Plugin { get; private set; }

public void OnImportsSatisfied()
{
Plugin = _plugin;
}
}
}

public interface IPlugin
{
void Method();
}

[Export(typeof(IPlugin))]
public class Plugin : IPlugin
{
public void Method()
{
//Method Blocks
Thread.Sleep(5000);
}
}

问题是对 plugin.Method() 的调用被阻塞了。为了解决这个问题,我们将暴露给控制台应用程序的接口(interface)更改为以下内容:

public interface IAsyncPlugin
{
Task Method();
}

调用此接口(interface)的实现不会阻塞。我们唯一需要更改的是 CompositionHost 类:

    private class CompositionHost: IPartImportsSatisfiedNotification
{
[Import(typeof (IPlugin))] private IPlugin _plugin;

public IAsyncPlugin Plugin { get; private set; }

public void OnImportsSatisfied()
{
Plugin = new AsyncPlugin(_plugin);
}

private sealed class AsyncPlugin : IAsyncPlugin
{
private readonly IPlugin _plugin;

public AsyncPlugin(IPlugin plugin)
{
_plugin = plugin;
}

public Task Method()
{
return Task.Factory.StartNew(() => _plugin.Method());
}
}
}
}

显然,这是一个非常简单的示例,在将其应用于您的 WPF 方案时,实现可能会略有不同 - 但一般概念应该仍然有效。

关于c# - MEF 插件架构的即发即弃方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19833323/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com