2013-11-07 2 views
8

Этот вопрос может быть связан с дизайном или кодом, но я застрял, поэтому я открыт для любого ответа; указатель в правильном направлении!Попытка и забыть подход к архитектуре плагина MEF

Я использовал 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 в основном приложение Thats получает данные , смотрит, какой плагин должен иметь его, а затем отправляет его (через функцию PlugginTrigger).

+0

Можете ли вы опубликовать упрощенную версию вашего интерфейса плагина и пример того, как приложение в настоящее время вызывает метод на одном из плагинов? – Matt

+0

(добавленный пример кода) – RobertN

ответ

4

Несколько наблюдений:

  • Огня и забыть это требование хозяина так не то, что вставные реализации должны беспокоиться о.
  • Я не думаю (пожалуйста, поправьте меня, если я ошибаюсь). CLR поддерживает методы вызова в режиме «fire-and-forget» в одном и том же 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() блокирует. Чтобы решить эту проблему, мы изменяем интерфейс, который подвергается воздействию консольного приложения на следующее:

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 - но общая концепция должна еще работать ,

+0

Ницца! Я бы не считал себя супер-программистом в любом случае, и, возможно, поэтому я не вижу, как я буду соответствовать классу CompositionHost в своем решении, поскольку я использую другой способ компоновки (используя DirectoryCatalog). Но это моя вина. Но я попробовал Task.Factory.StartNew -way вызова методов и сделал трюк, по крайней мере, так кажется! Но могу ли я не включать остальное предлагаемое решение? Или, может быть, я должен сначала попробовать стресс-тест какого-то типа (со многими pluggins cCalling друг с другом), чтобы увидеть, кажется ли это «нормально»? – RobertN

+0

Я думаю, что вы могли бы «Экспортировать» класс «CompositionHost» в вашей текущей структуре MEF и импортировать этот экземпляр в свое приложение, чтобы потреблять ваши плагины. Вы, конечно же, не должны включать остальное мое решение - все, что вы найдете полезным. Вы не ошибетесь с небольшим количеством тестов ... – Lawrence

Смежные вопросы