Применение ParaService на практических примерах

  !   Данная информация предназначена только только для IT-специалистов по системной интеграции модулей БИОСОФТ-М. (см. Руководства пользователя к программным продуктам)

Иллюстрации к документации в    ParaService для параллельных задач

не влезающие уже туда:

Сначала см. общие принципы в    ParaService Programmer Guide

Наиболее современная структура классов заложена в Absex - Параллельные процессы в шаблоне нового модуля и этот пример устарел.

Рекомендуемая структура проекта
Две роли

Создаем инкапсуляторы ролей процесса

 - CUportParamodClientIface - для вызова с целью получения прикладной функциональности и фабрик объектов ее реализующих независимо от того буду ли они отработаны inproc или вызовами в паралельный процесс (что решает Impl этого класса приватно).

 - CUportParamodServiceIface - отвечает приложению является ли оно парасервисом и если да то реализует системную функциональность требуемую только в сервисном прцессе..

Отдельный пакет для сервиса

Рекомендуется сначала вынести все прикладные функции, выполняемые сервисом в отдельный пакет. Если такого еще нет в приложении. В этом пакете не нагромождать логику коммуникации, запуска и обработки сообщений сервиса. Оставить этот пакет в таком виде, чтобы функции оттуда были доступны приложению и без применения параллельных процессов.

Создать отдельный пакет в котором расположить все классы необходимые как для запуска сервиса со стороны главного приложения и отсылки постов, так и обработчик постов самого сервиса, вызывающий в свою очередь прикладные функции из прикладных пакетов согласно принятым ми директивам от клиентов.

Если функциональность, которую мы хотим распараллелить мы расположили в пакете Packages/Foo, то обслуживание параллельности расположим в Packages/FooParaService. См. рис.

Последовательность внедрения
Абстракция для клиента

Для выбора реализации прикладных функций в нужном режиме (синхронном или параллельном) в инкапсуляторе для клиента вводим фабрику(и) объектов реализующих задачи либо локально в процессе либо отправляющие post

 class CUportParamodClientIface : public object
 {
 
 
 // Operations
 
     // This returns to the main app
     //   - either local implementation residing in the current process
     //   - or parallel implemetation operating via ParaService posting
     //   according to its internal settings.
     //   The app does not need to care about the details!
     ref<CUportIoIfaceGp> NewUportParamodIo()
             new virtual auto;
ref<CUportIoIface> CUportParamodClientImpl::OnNewUportParamodIo()
 {
     if (
         // am I a service process?
         CUportProject::GGetGlobalInstance().
             x_rUportLoader->
                 x_rUportParamodService->
                     IsStartedAsUportParamodParaService()
         ||
         // forced inproc execution in the client process?
         debslot("Paramod.InProcess?").InputBool())
     {
         return ref<CUportIoInprocIface>();
     }
     else
     {
         return ref<CUportParamodIo>();
     }
 }

Таким образом все остальное приложение не вникая в проблемы паралельности обращается к NewUportParamodIo() для получения очередного прикладного объекта.

В данном примере достаточно переключить ключ в дебслот чтобы отключить вызовы сервиса и выполнять все в главном процессе.

Дальше, как, например, реализовано в Uport: есть универсальный Impl для вычитывания данных порта, который выбирает внутренний Impl на основании того объекта, что вернет x_rUportParamodClient->NewUportParamodIo() и дальше все методы маршрутирезируются либо в in-proc реализацию либо в параллельный сервис:

ref<CUportIoIface> CUportIoImpl::GetImpl()
{
    if (!m_bPrivateActualUportIoInitOk)
    {
        m_bPrivateActualUportIoInitOk = true;
 
        m_rPrivateActualUportIo =
            CUportProject::GGetGlobalInstance().
                x_rUportLoader->
                    x_rUportParamodClient->
                        NewUportParamodIo();
    }
 
    return m_rPrivateActualUportIo;
}
 
bool CUportIoImpl::OnIsUportIoInitialized()
{
    return GetImpl()->IsUportIoInitialized();
}
 
int CUportIoImpl::OnGetUnimodPort()
{
    return GetImpl()->GetUnimodPort();
}
 
int CUportIoImpl::OnGetUnimodNode()
{
    return GetImpl()->GetUnimodNode();
}
 
void CUportIoImpl::OnInitUportIo(
        int iPort)
{
    GetImpl()->InitUportIo(iPort);
}
 
str CUportIoImpl::OnGetFullUsbReport()
{
    return GetImpl()->GetFullUsbReport();
}
 
void CUportIoImpl::Onx_nLostPackets_Set(
        int value)
{
    GetImpl()->x_nLostPackets = value;
}
 
...
Добавляем проперти в лоадер

) Добавляем в лоадер оба глобальных (и взаимоисключающих) объекта инкапсулирующих вызов парасервиса из клиентского процесса либо инициализирующих его в самом сервисе

 class CUportLoaderIface : public object
 {
 
 //VL: 2010-07-22
     // ParaService
     ref<CUportParamodClientIface> x_rUportParamodClient,
             auto(Get);
     ref<CUportParamodServiceIface> x_rUportParamodService,
             auto(Get);
 //VL.

Такая примитивная глобальная структура будет работать в большинстве случаев.

Усложнение ее может происходить если потребуется запускать N-ное количество сервисных процессов либо если будет реализовано (сейчас не поддерживается) реализация нескольких сервисов одним процессом.

Однако так или иначе в лоадере для данного проекта будут некие корневые контроллеры определяющие принципиально глобальные функции относящиеся к текущему процессу.

Эти проперти приложение использует когда уже знает сервис оно или клиент.

Запуск в режиме сервиса

При запуске в режиме сервиса мы не хотим показывать главное окно и инициализировать объекты обслуживающие основное приложение. Поэтому инициализацию сервиса вписывам сюда:

 void CStabipLoaderImpl::OnStartApplication()
 {
 //VL: 2010-07-04
     x_rStabipDizzyService->
         TryInitAsStabipDizzyServiceProcess();
     if (x_rStabipDizzyService->
             IsStartedAsStabipDizzyParaService())
     {
         return;
     }
 //VL.
 
     //  Init main app and show main window...

То есть пытаемся инициализировать инкапсулятор нашего процесса как сервис. Если это удалось то остальную обычную инициализацию приложения и главного окна не делаем.

Общение сервиса с внешним миром будет происходить не посредством его главного окна а путем приема им внешних команд, хандлер которых инициализирован если IsInit() здесь вернул true.

Инициализация хандлера сервиса

Реализуем методы инициализации процесса как ParaService вызванные выше:

 void CStabipDizzyServiceImpl::OnTryInitAsStabipDizzyServiceProcess()
 {
     if (_m_bOneTimeInitOk)
     {
         return;
     }
     _m_bOneTimeInitOk = true;
 
     if not_null(
         ref<CInterprocParaServiceIfaceGp> rInterprocParaService =
             _m_onpostStabipDizzy.
                 TryOpenParaServicePostOffice(
                     CStabipDizzyPost::C_sStabipDizzyPostId,
                     this))
     {
         _m_bWeAreTheParaServiceProcess = true;
 
         //
         // Init as ParaService
         //
 
         // for example: change process priority:
        sys::SetProcessPriorityClass(
             sys::E_ProcessPriority_High);
     }
 }
 bool CStabipDizzyServiceImpl::OnIsStartedAsStabipDizzyParaService()
 {
     rASSERT(_m_bOneTimeInitOk);
     return _m_bWeAreTheParaServiceProcess;
 }

Храним следующие данные поддерживающие состояние парасервиса:

 class CStabipDizzyServiceImpl : public CStabipDizzyServiceIface
 {
 private:
     bool _m_bOneTimeInitOk = false;
     bool _m_bWeAreTheParaServiceProcess = false;
 
     // This Post handler must adhere to PFN_Handler signature in onpost template<>
     //   This servies as a ParaService handler.
     void HandleStabipDizzyPost(
             ref<CStabipDizzyPost> rStabipDizzyPost);
     onpost<HandleStabipDizzyPost> _m_onpostStabipDizzy;

в классе нашего инкапсулятора сервиса.

Функция хандлера

Остается реализовать хандлер сообщений получаемых парасервисом от клиентов:

 void CStabipDizzyServiceImpl::HandleStabipDizzyPost(
         ref<CStabipDizzyPost> rStabipDizzyPost)
 {
     if (rStabipDizzyPost->x_bStateControl == )
            .........
     ......
 }

Если сервису не очевидно что от него требуется он может использовать данные присланные с постом. Опять же важно не забывать что это НЕ КОМАНДЫ а ОБНОВЛЕНИЕ СОСТОЯНИЯ которое требуется каким то из клиентов от сервиса.

Если сервис отрабатывает последовательность команд то их очередь, упорядочивание конфликтов между клиентами, повторы потеряных команд при рестартах сервиса и т.п. надо реализовывать отдельно существующими средствами через post или буфера разделяемой памяти, управляющей файлы и т.п. что выходит за пределы данного топика.