Я использую в своем прилождении 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);
.....
Здравствуйте, D.Triton, Вы писали: DT>Теперь запрос в базу через SqlTrackingQuery пойдет только после коммита в базу.
Любопытное решение.
Вопрос — после какого коммита? Насколько я понимаю — любого. Код-то многопоточный?
P.S. Не в первый раз даю этот совет — откажитесь вы от этой идеи. Ну не для того SqlTrackingQuery придуман.
Сделайте ручками и будет вам счастье. И по скорости и по надежности. И даже по трудозатратам
Если инструмент вместо того, чтобы решать вашу задачу, заставляет вас бегать вокруг него с бубном, значит инструмент выбран неправильно.
А если уж хочется все сделать именно через... SqlTrackingQuery...
Наверное, вам нужно подключиться к сохранению своего workflow через IPendingWork, а в методе Commit вызывать метод, который просигналит событие для этой конкретной workflow. Ну а в коде, который ждет, вызывать другой метод, который опять же по id workflow найдет event, на котором нужно ждать. Как-то так.
Или просто периодически в цикле опрашивать DB на предмет появления нужной записи.
Здравствуйте, 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 на предмет появления нужной записи.
Не думаю.
Здравствуйте, 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.
Здравствуйте, 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 данные закоммичены.
Здравствуйте, D.Triton, Вы писали: DT>Спасибо, недосмотрел. Но по сути, кардинально это ничего особо не меняет. DT>Код довольно лаконичен и выполняет поставленную задачу — гарантирует, что перед запросом в базу через SqlTrackingQuery данные закоммичены.
Насколько я понимаю, он гарантирует только то, что будут закоммичены хоть чьи-нибудь данные. Т.е. в случае параллельной работы двух пользователей они оба могут "среагировать" на сохранение одного из них.
DT>Благодарю за критику.
У меня еще и рацпредложение есть. Безвозмездно, т.е. — даром.
Сделайте в базе новую таблицу. Поля — WorkflowID, CurrentStateName, Property1Value, Property2Value...
Вставьте в каждый State обработчик инициализации. В теле обработчика начального состояния делайте insert, в промежуточных — update, в обработчике завершения работы — delete. Поиск/фильтрацию/обновление данных перепишите с запроса к SqlTrackingQuery на запрос к этой таблице. Это займет часа 4, я думаю.
Здравствуйте, mrozov, Вы писали:
M>Здравствуйте, D.Triton, Вы писали: DT>>Спасибо, недосмотрел. Но по сути, кардинально это ничего особо не меняет. DT>>Код довольно лаконичен и выполняет поставленную задачу — гарантирует, что перед запросом в базу через SqlTrackingQuery данные закоммичены.
M>Насколько я понимаю, он гарантирует только то, что будут закоммичены хоть чьи-нибудь данные. Т.е. в случае параллельной работы двух пользователей они оба могут "среагировать" на сохранение одного из них.
DT>>Благодарю за критику. M>У меня еще и рацпредложение есть. Безвозмездно, т.е. — даром. M>Сделайте в базе новую таблицу. Поля — WorkflowID, CurrentStateName, Property1Value, Property2Value... M>Вставьте в каждый State обработчик инициализации. В теле обработчика начального состояния делайте insert, в промежуточных — update, в обработчике завершения работы — delete. Поиск/фильтрацию/обновление данных перепишите с запроса к SqlTrackingQuery на запрос к этой таблице. Это займет часа 4, я думаю.
Здравствуйте, mrozov, Вы писали:
M>Здравствуйте, D.Triton, Вы писали: DT>>Спасибо, недосмотрел. Но по сути, кардинально это ничего особо не меняет. DT>>Код довольно лаконичен и выполняет поставленную задачу — гарантирует, что перед запросом в базу через SqlTrackingQuery данные закоммичены.
M>Насколько я понимаю, он гарантирует только то, что будут закоммичены хоть чьи-нибудь данные. Т.е. в случае параллельной работы двух пользователей они оба могут "среагировать" на сохранение одного из них.
У меня сделано, что WCF сервис работает как singleton.
а инстанс 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 при выполнении операции коммита выглядит следующим образом:
Исходя из того, что у меня все воркфлоу в 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 или прийдется строить какой-то адаптер к новой версии. Вообще, одни проблемы. Короче говоря, работать с базой не очень хочется, хотя уже пришлось....