Child process executor
[csharp  | 
        
            rpc 
        
    ]
    
    
  Abstract
Механизм запуска задач в дочернем процессе, локальном либо удалённом. Позволяет освободить основные сборки решения от нативных зависимостей и даёт возможность параллельного исполнения.
Introduction
Есть крупный сервис обработки данных, разной обработки разных данных. В основном обработка производится с помощью сторонних библиотек, большая часть из которых нативная (C, C++, Pascal), а некоторые защищены железными ключами (HASP).
Основной процесс сервиса x64, соответственно большая часть сборок либо x64, либо AnyCPU. Но часть библиотек не имеет x64 версии, а отказаться от них просто так нельзя; часть задач необходимо выполнять на конкретных машинах, потому что в них воткнуты ключи с лицензиями.
Возникла потребность выполнения определённых задач вне основного процесса, в том числе удалённо.
Основной сценарий работы с библиотеками состоит из двух этапов: инициализация и обработка; в большинстве случаев время обработки многократно превышает время инициализации.
Methods
Всю логику запуска определённой задачи в дочернем процессе, на локальной или удалённой машине вынесена в отдельный проект (на гитхабе).
Состав решения:
- 
    
MasterProcess (x64) - используется главным процессом сервиса, запускает задачу, получает результат.
 - 
    
CommonAssembly (AnyCPU) - общая для всех сборка; содержит классы для передачи параметров запуска и обработки задачи.
 - 
    
ChildProcessExecutor (x86 и x64) - логика запуска и выполнения указанной задачи.
 - 
    
TaskAssembly (x86 и x64) - сборка задачи; логика работы с конкретной библиотекой.
 
Ограничения зависимостей:
- 
    
Сборки главного процесса (в том числе MasterProcess) не должны иметь нативных зависимостей или ссылок на сборки с жёсткой привязкой к x86. Они имеют ссылки только на CommonAssembly и TaskAssembly, но не инстанциируют никаких типов из последней!
 - 
    
Сборка CommonAssembly также не должна иметь нативных зависимостей или ссылок на сборки с жёсткой привязкой к x86.
 - 
    
Сборка ChildProcessExecutor собирается в разные битности в зависимости от битности сборок задач. Обычно на каждой машине лежит две этих сборки с разными битностями.
 - 
    
Сборок TaskAssembly может и будет много: на каждый тип задачи (со своими нативными зависимостями) своя.
 
Как это всё работает:
- 
    
Головной процесс запускает executor, либо напрямую (если тот расположен локально), либо через утилиту PsExec из пакета SysInternals (если удалённо).
 - 
    
Executor запускается, получая в качестве аргументов режим запуска и параметры канала связи (net.pipe либо http) с головным процессом.
 - 
    
Executor запрашивает у головного процесса параметры запуска задачи (в том числе путь до сборки задачи плюс параметры инициализации библиотеки).
 - 
    
Executor загружает сборку задачи (с зависимостями) и инициализирует её. Сборка может быть в другом месте файловой системы, нежели чем сам executor, но обязательно на той же машине.
 - 
    
Executor запускает обработку с указанными параметрами и отправляет результат головному процессу.
 
Results
Что получилось в итоге: описанный выше механизм успешно используется в коммерческом продукте, гибко настраивается и хорошо масштабируется.
Код на гитхабе.