这个问题可能与设计有关,也可能与代码有关,但我被困住了,所以我对任何答案持开放态度;一个正确的指针!
我已经使用MEF(Managed Extensibility Framework)来开发一个WPF软件,它将作为插件的协调器形式。应用程序只是在用户选择的插件之间重定向数据,因此该插件所做的事情根本不知道(特别是因为它们可以由第三方开发人员开发)。应用程序和插件共享一个接口,作为了解要调用的方法的一种方式,因此流量有两种方式:插件调用主应用程序中的方法发送数据,主应用程序将此数据传递给另一个插件。
这到目前为止工作,但我遇到了同步行为的问题。接口定义的所有方法都缺少返回值(Void),而我正在努力获得一种“即发即忘”的方法,其中调用应用程序不需要等待插件接收函数来完成执行代码(和调用回到主应用程序!)。
那么解决这个问题的最佳方法是什么?让每个插件(和主应用程序)将它的工作量放在某种类型的“堆栈”上,只是为了能够将控件返回到调用端,然后有一些单独运行的机制,逐个项目地执行堆栈(和这种堆叠方法是async吗?)?
其他值得注意的事情是插件在不同的线程中运行(根据调试器线程窗口),并且当它们被初始化时,它们从调用主应用程序获得引用,以便它们可以在主应用程序中触发函数。插件也经常需要告诉主应用程序它们处于什么状态(空闲,工作,错误等),并且还要发送主应用程序要记录的数据,因此这通常会创建一个嵌套的调用层次结构(如果您关注我, 难以解释)。
我正在使用.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函数)。
几点意见:
IsOneWay
property上设置OperationContractAttribute
即可。第二点提出了一个解决方案,对你的情况来说似乎有些过分 - 但无论如何我们都要提一下。您的插件可以托管进程内WCF服务,WPF应用程序和插件之间的所有通信都可以通过WCF服务代理完成。然而,这伴随着配置的噩梦,并且真正打开了一堆蠕虫,你需要解决一大堆其他问题。
让我们从一个简单的初始问题示例开始,并尝试从那里解决它。以下是带有插件的Console应用程序的代码:
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()
的调用是阻塞的。为解决此问题,我们将暴露给控制台应用程序的界面更改为以下内容:
public interface IAsyncPlugin
{
Task Method();
}
对此接口的实现的调用不会阻止。我们唯一需要改变的是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场景时,实现可能必须略有不同 - 但是一般概念应该仍然有效。