[WWF] SqlTrackingService
От: D.Triton Украина  
Дата: 26.03.08 12:01
Оценка:
Здравстуйте.

Каким образом можно определить, что SqlTrackingService сбросил данные в БД ?



Проблема в следующем (проявляется только в дэплойменте у нас и у заказчика. Локально в запущенном в дэбаге что у нас, что у заказчика не проявляется):

Использую WWF, коротый хоститься на WCF сервисе, который, в свою очередь, обитает на IIS 6.

Использую .net 3 (3.5 пока не могу использовать).

Вот конфигурация рантайма


<WorkflowRuntime>
        <CommonParameters>
            <add name="ConnectionString" value="Data Source=localhost\SQL2005;Initial Catalog=MyDb;Integrated Security=True;" />
        </CommonParameters>
        <Services>
            <add type="System.Workflow.Activities.ExternalDataExchangeService, System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" ConfigurationSection="ExternalDataExchangeServices"/>
            <add type="System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService"/>
            <add type="System.Workflow.Runtime.Tracking.SqlTrackingService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" ConnectionString="Data Source=localhost\SQL2005;Initial Catalog=MyDb;Integrated Security=True;"/>
            <add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"  UnloadOnIdle="true" />
            <add type="System.Workflow.Runtime.Hosting.SharedConnectionWorkflowCommitWorkBatchService"/>
        </Services>
    </WorkflowRuntime>



Я использую в своем прилождении StateMachine Workflows (унаследованные от StateMachineWorkflowActivity).


Проблема в том, что после создания workflow или после перевода workflow из одного состояния в другое мне необходимо получить у
workflow значение определенных св-в, которые являются DependencyProperty и трэкаются в базу посредством SqlTrackingService.
Кроме того это необходимо получать непосредственно после вызова
ManualWorkflowSchedulerService s =
WorkflowRuntime.GetService<ManualWorkflowSchedulerService>();
s.RunWorkflow(workflowInstanceGuid);

SqlTrackingQuery tQuery = new SqlTrackingQuery(connectionString);
.....


Так вот, оказывается что после того, как я "зашедулил workflow" трэкинг данные не успевают "сброситься" в трэкинговую БД.

Обратите внимание на параметр UnloadOnIdle="true"
И, соотв., я могу получить последние значения параметров.

Но!

Если сделать хак (мне за него порядком стыдно, но что тут поделаешь: "На безрыбье и рак рыба" ), работает!

ManualWorkflowSchedulerService s =
WorkflowRuntime.GetService<ManualWorkflowSchedulerService>();
s.RunWorkflow(workflowInstanceGuid);
Thread.Sleep(2000);
SqlTrackingQuery tQuery = new SqlTrackingQuery(connectionString);
.....


Вот раскопал пост от г-на Jo&#227;o Ricardo Pereira. У них аналогичная проблема.

Если кто-то сталкивался с данной проблемой, прошу отозваться.

Благодарю за помощь.
Re: [WWF] SqlTrackingService
От: D.Triton Украина  
Дата: 31.03.08 14:35
Оценка: 5 (1)
Проблема решена.

Вот измененная конфигурация рантайма.

Пришлось модифицировать шедулер, который бы информировал о коммите в базу.

<WorkflowRuntime>
        <CommonParameters>
            <add name="ConnectionString" value="Data Source=localhost\SQL2005;Initial Catalog=MyDb;Integrated Security=True;" />
        </CommonParameters>
        <Services>
            <add type="System.Workflow.Activities.ExternalDataExchangeService, System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" ConfigurationSection="ExternalDataExchangeServices"/>
            <add type="System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService"/>
            <add type="System.Workflow.Runtime.Tracking.SqlTrackingService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" ConnectionString="Data Source=localhost\SQL2005;Initial Catalog=MyDb;Integrated Security=True;"/>
            <add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"  UnloadOnIdle="true" />
            <add type="AST.PartnerSite20.WF.WorkflowService.Utils.NotifierSharedConnectionWorkflowCommitWorkBatchService, WF.WorkflowService, PublicKeyToken=null"/>
        </Services>
    </WorkflowRuntime>




using System;
using System.Collections.Specialized;
using System.Workflow.Runtime.Hosting;

namespace AST.PartnerSite20.WF.WorkflowService.Utils
{
    public class NotifierSharedConnectionWorkflowCommitWorkBatchService : SharedConnectionWorkflowCommitWorkBatchService
    {
        public NotifierSharedConnectionWorkflowCommitWorkBatchService(NameValueCollection parameters) : base(parameters)
        {
        }

        public NotifierSharedConnectionWorkflowCommitWorkBatchService(string connectionString) : base(connectionString)
        {
        }

        public event EventHandler Commited;

        protected override void CommitWorkBatch(CommitWorkBatchCallback commitWorkBatchCallback)
        {
            base.CommitWorkBatch(commitWorkBatchCallback);
            EventHandler aCommited = Commited;
            if (aCommited != null)
                aCommited(this, EventArgs.Empty);
        }
    }
}



Теперь код переводящий из состояния в состояние инстанс или создающий его
будет выглядеть так

            ManualWorkflowSchedulerService s =
                WorkflowRuntime.GetService<ManualWorkflowSchedulerService>();
            using (AutoResetEvent aResetEvent = new AutoResetEvent(false))
            {
                NotifierSharedConnectionWorkflowCommitWorkBatchService aService =
                    theWorkflowRuntime.GetService<NotifierSharedConnectionWorkflowCommitWorkBatchService>();
                EventHandler aCommited = delegate { aResetEvent.Set(); };

                aService.Commited += aCommited;
                s.RunWorkflow(argInstanceId);
                aResetEvent.WaitOne();
                aService.Commited -= aCommited;
            }
            SqlTrackingQuery tQuery = new SqlTrackingQuery(connectionString);
            .....


Теперь запрос в базу через SqlTrackingQuery пойдет только после коммита в базу.

На данный момент наличие параметра UnloadOnIdle="true" обязательно.
Re[2]: [WWF] SqlTrackingService
От: mrozov  
Дата: 31.03.08 19:23
Оценка:
Здравствуйте, D.Triton, Вы писали:
DT>Теперь запрос в базу через SqlTrackingQuery пойдет только после коммита в базу.
Любопытное решение.
Вопрос — после какого коммита? Насколько я понимаю — любого. Код-то многопоточный?

P.S. Не в первый раз даю этот совет — откажитесь вы от этой идеи. Ну не для того SqlTrackingQuery придуман.
Сделайте ручками и будет вам счастье. И по скорости и по надежности. И даже по трудозатратам
Если инструмент вместо того, чтобы решать вашу задачу, заставляет вас бегать вокруг него с бубном, значит инструмент выбран неправильно.

А если уж хочется все сделать именно через... SqlTrackingQuery...
Наверное, вам нужно подключиться к сохранению своего workflow через IPendingWork, а в методе Commit вызывать метод, который просигналит событие для этой конкретной workflow. Ну а в коде, который ждет, вызывать другой метод, который опять же по id workflow найдет event, на котором нужно ждать. Как-то так.

Или просто периодически в цикле опрашивать DB на предмет появления нужной записи.
Re[3]: [WWF] SqlTrackingService
От: D.Triton Украина  
Дата: 31.03.08 20:47
Оценка:
Здравствуйте, mrozov, Вы писали:

M>Здравствуйте, D.Triton, Вы писали:

DT>>Теперь запрос в базу через SqlTrackingQuery пойдет только после коммита в базу.
M>Любопытное решение.
M>Вопрос — после какого коммита? Насколько я понимаю — любого. Код-то многопоточный?
Поскольку в параметрах рантайма установлен ManualWorkflowSchedulerService(WorkflowRuntime хостится в WCF сервисе, который хостится на IIS),
то, если я не ошибаюсь, Workflow запускается в синхронном режиме.



public bool RunWorkflow(
    Guid workflowInstanceId
)


This is a synchronous call that uses the current thread to run the workflow. It does not return until the workflow idles, suspends, completes, terminates, or aborts.


Итак, ранее, после создания workflow instance или после перевода инстанса из одного состояния в другое, мне требовалось
опросить значения параметров этого инстанса.

Скажем задача такая. Есть како-то бизнес-процесс, у него есть ряд св-в. Группа пользователей системы может взаимодействовать с данным бизнес-процессом
(с одним процессом может взаимодействовать одновременно n-е кол-во пользователей).
Каждому процессу соотв. Workflow instance, который определяет изменение состояния ( процесс представлен посредством конечного автомата).
Пользователи могут создавать, удалять, переводить из состояния в состояние эти "workflow". Во время создания или переведения из состояния в состояние
внутренние св-ва workflow (DependencyProperty) могут изменяться.

После каждого такого изменения или создания, пользователю требуется отобразить новые значения этих св-в.

Чтобы получить значения этих св-в у незавершенного WorkflowInstance у Майкрософта написано, что это можно сделать только через TrackingService.

Соотв. я раньше так и делал:

ManualWorkflowSchedulerService s =
WorkflowRuntime.GetService<ManualWorkflowSchedulerService>();
s.RunWorkflow(workflowInstanceGuid);

SqlTrackingQuery tQuery = new SqlTrackingQuery(connectionString);
.....


Но затем, спустя некоторое время, "начался хаос". И проявлялся он только в продакшене и только под IIS6

В момент запроса данных в вышепривденном коде через SqlTrackingQuery было замечено, что
возвращались данные о предыдущем остоянии Workflow .

Я нашел решение: делать Thread.Sleep(), на время, пока данные "сольются" в базу. Но приведенное решение не выдерживает никакой критики, хотя и работает

Далее я нашел, приведенный мной в первом посте, запись на соотв. форуме msdn, людей, которые столкнулись с похожей проблемой. Но они решения так и не нашли.

В итоге, вооружившись рефлектором, я начал раскапывать как работает "вся эта кухня".

В итоге, мне требовалось определить момент, когда данные были бы закомичены в базу. И только после этого момента, я должен был использовать SqlTrackingQuery.

Сначала я пытался использовать событие WorkflowRuntime.WorkflowPersisted. Но ничего не вышло.
Если я не ошибся в логике работы (привожу сильно упрощенную модель работы алгоритма), то
сначала создавалась "обрамляющая" транзакция через TransactionScope, затем вполнялся workflow и после перехода workflow в состояние Idle (UnloadOnIdle="true") данные записывались в базу, затем шел коммит в базу (внутренний DbTransaction),
затем стреляло событие WorkflowPersisted (и в нем я не получал данных из базы ввиду незакомиченной "обрамляющей"), а уже после него стрелял коммит "обрамляющей" транзакции.

В итоге пришлось использовать свой Scheduler, который бы сообщил, что данные окончательно закомиченные и я могу их опросить через SqlTrackingQuery.

Вот такая история. Конечно было бы проще, если бы M$ открыла исходный код, чтобы можно было интерактивно отлаживать.

M>P.S. Не в первый раз даю этот совет — откажитесь вы от этой идеи. Ну не для того SqlTrackingQuery придуман.

M>Сделайте ручками и будет вам счастье. И по скорости и по надежности. И даже по трудозатратам
Да уже просто некогда . По поводу скорости, пришлось написать свой сервис кэширования данных о состоянии Workflow, т.к. дергать каждый раз SqlTrackingQuery вылевалось в приличные "тормоза".
M>Если инструмент вместо того, чтобы решать вашу задачу, заставляет вас бегать вокруг него с бубном, значит инструмент выбран неправильно.
Согласен.
M>А если уж хочется все сделать именно через... SqlTrackingQuery...
M>Наверное, вам нужно подключиться к сохранению своего workflow через IPendingWork, а в методе Commit вызывать метод, который просигналит событие для этой конкретной workflow. Ну а в коде, который ждет, вызывать другой метод, который опять же по id workflow найдет event, на котором нужно ждать. Как-то так.
Пока не готов коментировать. Нужно подумать.
M>Или просто периодически в цикле опрашивать DB на предмет появления нужной записи.
Не думаю.
Re[4]: [WWF] SqlTrackingService
От: mrozov  
Дата: 01.04.08 05:01
Оценка:
Здравствуйте, D.Triton, Вы писали:
DT>Поскольку в параметрах рантайма установлен ManualWorkflowSchedulerService(WorkflowRuntime хостится в WCF сервисе, который хостится на IIS),
DT>то, если я не ошибаюсь, Workflow запускается в синхронном режиме.

The ManualWorkflowSchedulerService provides a threading service that enables the host application that creates a workflow instance to donate the Thread on which the workflow instance is run. Using this threading service, host applications can run a workflow instance on a single Thread (that is, in synchronous mode). This mode blocks the execution of the host application until the workflow instance becomes idle. Subsequently, the workflow instance can only be executed by using the RunWorkflow method of this service.

ManualWorkflowSchedulerService controls the number of threads spawned in an ASP.NET process by reusing the thread that made the ASP.NET Web request to run the workflow instance. This ensures that at any time, the number of active threads in the workflow runtime equals the number of active Web requests in the ASP.NET process.

Re[5]: [WWF] SqlTrackingService
От: D.Triton Украина  
Дата: 01.04.08 06:53
Оценка:
Здравствуйте, mrozov, Вы писали:

M>Здравствуйте, D.Triton, Вы писали:

DT>>Поскольку в параметрах рантайма установлен ManualWorkflowSchedulerService(WorkflowRuntime хостится в WCF сервисе, который хостится на IIS),
DT>>то, если я не ошибаюсь, Workflow запускается в синхронном режиме.
M>

M>The ManualWorkflowSchedulerService provides a threading service that enables the host application that creates a workflow instance to donate the Thread on which the workflow instance is run. Using this threading service, host applications can run a workflow instance on a single Thread (that is, in synchronous mode). This mode blocks the execution of the host application until the workflow instance becomes idle. Subsequently, the workflow instance can only be executed by using the RunWorkflow method of this service.

M>ManualWorkflowSchedulerService controls the number of threads spawned in an ASP.NET process by reusing the thread that made the ASP.NET Web request to run the workflow instance. This ensures that at any time, the number of active threads in the workflow runtime equals the number of active Web requests in the ASP.NET process.

Спасибо, недосмотрел. Но по сути, кардинально это ничего особо не меняет.
Код довольно лаконичен и выполняет поставленную задачу — гарантирует, что перед запросом в базу через SqlTrackingQuery данные закоммичены.


Благодарю за критику.
Re[6]: [WWF] SqlTrackingService
От: mrozov  
Дата: 01.04.08 07:57
Оценка:
Здравствуйте, D.Triton, Вы писали:
DT>Спасибо, недосмотрел. Но по сути, кардинально это ничего особо не меняет.
DT>Код довольно лаконичен и выполняет поставленную задачу — гарантирует, что перед запросом в базу через SqlTrackingQuery данные закоммичены.

Насколько я понимаю, он гарантирует только то, что будут закоммичены хоть чьи-нибудь данные. Т.е. в случае параллельной работы двух пользователей они оба могут "среагировать" на сохранение одного из них.

DT>Благодарю за критику.

У меня еще и рацпредложение есть. Безвозмездно, т.е. — даром.
Сделайте в базе новую таблицу. Поля — WorkflowID, CurrentStateName, Property1Value, Property2Value...
Вставьте в каждый State обработчик инициализации. В теле обработчика начального состояния делайте insert, в промежуточных — update, в обработчике завершения работы — delete. Поиск/фильтрацию/обновление данных перепишите с запроса к SqlTrackingQuery на запрос к этой таблице. Это займет часа 4, я думаю.
Re[7]: [WWF] SqlTrackingService
От: D.Triton Украина  
Дата: 01.04.08 10:03
Оценка:
Здравствуйте, mrozov, Вы писали:

M>Здравствуйте, D.Triton, Вы писали:

DT>>Спасибо, недосмотрел. Но по сути, кардинально это ничего особо не меняет.
DT>>Код довольно лаконичен и выполняет поставленную задачу — гарантирует, что перед запросом в базу через SqlTrackingQuery данные закоммичены.

M>Насколько я понимаю, он гарантирует только то, что будут закоммичены хоть чьи-нибудь данные. Т.е. в случае параллельной работы двух пользователей они оба могут "среагировать" на сохранение одного из них.


DT>>Благодарю за критику.

M>У меня еще и рацпредложение есть. Безвозмездно, т.е. — даром.
M>Сделайте в базе новую таблицу. Поля — WorkflowID, CurrentStateName, Property1Value, Property2Value...
M>Вставьте в каждый State обработчик инициализации. В теле обработчика начального состояния делайте insert, в промежуточных — update, в обработчике завершения работы — delete. Поиск/фильтрацию/обновление данных перепишите с запроса к SqlTrackingQuery на запрос к этой таблице. Это займет часа 4, я думаю.

Спасибо, попробую.
Re[7]: [WWF] SqlTrackingService
От: D.Triton Украина  
Дата: 01.04.08 10:45
Оценка:
Здравствуйте, mrozov, Вы писали:

M>Здравствуйте, D.Triton, Вы писали:

DT>>Спасибо, недосмотрел. Но по сути, кардинально это ничего особо не меняет.
DT>>Код довольно лаконичен и выполняет поставленную задачу — гарантирует, что перед запросом в базу через SqlTrackingQuery данные закоммичены.

M>Насколько я понимаю, он гарантирует только то, что будут закоммичены хоть чьи-нибудь данные. Т.е. в случае параллельной работы двух пользователей они оба могут "среагировать" на сохранение одного из них.

У меня сделано, что WCF сервис работает как singleton.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    internal class WorkflowService : IWorkflowService
    {
      //................
    }


а инстанс WorkflowRuntime я хряню в соотв. экстеншене
internal class WfWcfExtension : IExtension<InstanceContext>, IDisposable
    {

     }


По идее, все запросы от клиентского приложения будут обрабатываться поочереди.
Исходя из того, что выставлен UnloadOnIdle="true", то при достижении Idle состояния workflow начинает персиститься.


Делаем бряку на анонимный делегат

            ManualWorkflowSchedulerService s =
                WorkflowRuntime.GetService<ManualWorkflowSchedulerService>();
            using (AutoResetEvent aResetEvent = new AutoResetEvent(false))
            {
                NotifierSharedConnectionWorkflowCommitWorkBatchService aService =
                    theWorkflowRuntime.GetService<NotifierSharedConnectionWorkflowCommitWorkBatchService>();
                EventHandler aCommited = delegate { aResetEvent.Set(); };

                aService.Commited += aCommited;
                s.RunWorkflow(argInstanceId);
                aResetEvent.WaitOne();
                aService.Commited -= aCommited;
            }
            SqlTrackingQuery tQuery = new SqlTrackingQuery(connectionString);
            .....


CallStack при выполнении операции коммита выглядит следующим образом:

    WF.WorkflowService.DLL!AST.PartnerSite20.WF.WorkflowService.WorkflowInstanceManager.ManualSchedule.AnonymousMethod() Line 243 + 0x1 bytes    C#
       WF.WorkflowService.DLL!AST.PartnerSite20.WF.WorkflowService.Utils.NotifierSharedConnectionWorkflowCommitWorkBatchService.CommitWorkBatch(System.Workflow.Runtime.Hosting.WorkflowCommitWorkBatchService.CommitWorkBatchCallback commitWorkBatchCallback = {System.Workflow.Runtime.Hosting.WorkflowCommitWorkBatchService.CommitWorkBatchCallback}) Line 32 + 0x12 bytes    C#
     System.Workflow.Runtime.dll!System.Workflow.Runtime.WorkflowExecutor.CommitTransaction(System.Workflow.ComponentModel.Activity activityContext) + 0x65 bytes    
     System.Workflow.Runtime.dll!System.Workflow.Runtime.WorkflowExecutor.Persist(System.Workflow.ComponentModel.Activity dynamicActivity, bool unlock, bool needsCompensation) + 0x39d bytes    
     System.Workflow.Runtime.dll!System.Workflow.Runtime.WorkflowExecutor.ProtectedPersist(bool unlock) + 0x2b bytes    
     System.Workflow.Runtime.dll!System.Workflow.Runtime.WorkflowExecutor.PerformUnloading(bool handleExceptions) + 0x156 bytes    
     System.Workflow.Runtime.dll!System.Workflow.Runtime.WorkflowExecutor.RunScheduler() + 0x2e5 bytes    
     System.Workflow.Runtime.dll!System.Workflow.Runtime.WorkflowExecutor.RunSome(object ignored) + 0x12a bytes    
     System.Workflow.Runtime.dll!System.Workflow.Runtime.Hosting.DefaultWorkflowSchedulerService.WorkItem.Invoke(System.Workflow.Runtime.Hosting.WorkflowSchedulerService service = {System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService}) + 0x71 bytes    
     System.Workflow.Runtime.dll!System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService.RunOne(System.Guid workflowInstanceId) + 0x1a1 bytes    
     System.Workflow.Runtime.dll!System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService.RunWorkflow(System.Guid workflowInstanceId) + 0xe0 bytes



Исходя из того, что у меня все воркфлоу в Idle состоянии всегда заперсистены и WCF сервис обслуживает всех поочереди, то думаю,
что коммит будет как раз относиться непосредственно к workflow который я schedul-ю.

DT>>Благодарю за критику.

M>У меня еще и рацпредложение есть. Безвозмездно, т.е. — даром.
M>Сделайте в базе новую таблицу. Поля — WorkflowID, CurrentStateName, Property1Value, Property2Value...
M>Вставьте в каждый State обработчик инициализации. В теле обработчика начального состояния делайте insert, в промежуточных — update, в обработчике завершения работы — delete. Поиск/фильтрацию/обновление данных перепишите с запроса к SqlTrackingQuery на запрос к этой таблице. Это займет часа 4, я думаю.

При работе с базой из workflow могут появиться неприятные моменты.
К примеру, у меня имеются workflow instances c версией 1.0.0.0.
Они работают с базой.
Далее, по истечении некоторого времени меняется струкра базы и появляется версия workflow 1.1.0.0, которая умеет с ней работать.
Но у нас есть старые (заперсистенные версии 1.1.0.0), которые уже не умеют работать с новой структурой базы.
Тут прийдется городить какую-то фабрику, которая в зависимости от версии workflow возвращала бы нужную версию DAL или прийдется строить какой-то адаптер к новой версии. Вообще, одни проблемы. Короче говоря, работать с базой не очень хочется, хотя уже пришлось....
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.