
A while ago I was doing some technical research on WF: While my workflow was waiting for input, it also had to support some kind of timeout functionality. It also had to be possible to configure the duration of the timeout outside of the workflow (eg app.config), without the need to redeploy or restart the workflow.
Being a newby on the WF-platform, I simply expected the out-of-the-box Delay-activity to provide such functionality. I found out that in a Listen-activity containing an input-activity in one branch and the Delay-activity in another would work fine.
I would use the InitializeTimeoutDuration-event of the Delay-activity to read the configured timeout duration from the app.config. I soon found out I would need such functionality in several locations in the workflow, resulting in many code-blocks for each implementation of the InitializeTimeoutDuration-event.
This was obviously getting pretty messy, so I decided to create a custom activity which would derive from the standard Delay-activity, include the load-duration-from-config logic and supply a 'DelayName'-property to specify which configured duration to use. I soon got pretty frustrated when I found out that most out-of-the-box activities where marked 'sealed' (Microsoft: "though we would like make our product as extensible as possible, we had to prioritize where we'd focus this extensibility for this version and we decided to focus at the component model level, instead of on the out-of-box activities"), thus impossible to derive from.
I solved this by creating a composite activity which itself contained a Code-activity to load the duration from config and a Delay-activity which would initialize itself with the loaded duration.
Although this custom activity worked fine and cleaned up a lot of code from my workflow, I soon found out that it didn't work with StateMachine-workflows. In short: the EventDriven-activity in SM-workflow supports just one activity implementing IEventActivity and it must be the first activity. Since I encapsulated it in my custom activity, it would always be a child-activity of custom activity, so it would never be the first activity. For now, I decided to go for the ugly implementation: standard delay-activities, combined with workflow code to load durations from config.
… But my frustration didn't end there! When testing the application, it turned out the standard Delay-activity wasn't flawless! It supported delays up to 47 days, while we needed delays to last up to 4 months. I registered the issue with MS and also created a PSS-call, but because it turned out to be a bug in the .NET framework itself, MS decided to only fix it for the 3.5 framework, leaving me (and my customer) in the dark! 🙁
To get this all over with, I finally decided to create a custom delay activity by implementing the IEventActivity-interface myself. Although the standard Delay-activity could not be inherited from, it could be reflectored! I copied most code, added logic to cut the huge durations into smaller peaces of max. 47 days (the workflow is loaded every 47 days, then unloaded again if the total duration hasn't passed yet). While I was busy, I also included the logic to load the duration from config and finally came up with the Delay-activity that did everything I wanted and supported huge durations!
!!! The above mentioned issue has been fixed since .NET Framework 3.0 SP1 !!!
Check my comment below for more information and how to fix the sample-code!
You can download the source-code for the activity here: RobertKa.Samples.zip
(when downloading the source-code, you are obligated to send me all your useful additions to the code )
9 comments
Good stuff. I have had the same frustration with the with the delay activity.
Joe B
Is there any way to update the delay duration in runtime?
Currently it seems it can either be initialized from a config file, from the default property or in the initialize event, I want to be able to update the duration after it started running, and it seems very duable in your implementation.
Itai
Currently modifications of the durations only effect new instances of the workflow. This solves part of your problem. If order to have it also effect running workflows, the activity needs some modification, but not much! So yes, very much do-able.
It would require two things:
– Keep the MAX_INTERVAL_DURATION short so the workflow gets reactivated every few days or so (maybe set the ‘check-duration’ as a property on the activity)
– On reactivate, first refresh the _expiredUtc according to the config-file, before evaluating if the activity can be closed (finished).
I’ve thought of this before, but had no business requirement (or free time ;)) to cover the required effort.
robertka
Tks a lot! I really enjoy it! Great job!
Eduardo Bastos
Yesterday we found out that this activity does not work in a specific situation. When all these criteria are met, you may run into this issue as well:
– ManualWorkflowScheduler is used
– The activity is used for delays > 47 days
– The activity is used in a statemachine-workflow
– The state that contains the EventDriven-activity for this activity contains other EventDriven-activities as well.
This issue is that events for the other EventDriven-activities are no longer delivered and the workflow is persisted in a useless (unfixable) state. This behaviour of WF is undocumented, but sadly it does happen.
BUT! We also found out that MS has finally solved the ‘long-delay’-bug which I mentioned in this article. Although it’s not mentioned in the changelogs, it turns out they fixed it in .NET Framework 3.0 SP1!
So when using the Configurable Delay activity for sequential workflows, there’s no problem, but when using it for state machines, you need to upgrade to .NET 3.0 SP1 (or 3.5 ofcoz!) and remove the ‘duration splitting’-code from the activity.
robertka
My state machine workflow (.net 3.0 sp1) is throwing an exception when timer kicks in ..and after that I am having lot of unexpected behavior..like unable to raise events on the same workflow…is there way that I can trace what exception was thrown when my timer (delayactivity) kicked in ? I can see from workflow_instance_status table that exception has occured ..but where do I get the exception details from?
vasu
Vasu, are you using the ConfigurableDelayActivity (or the SafeDelayActivity)? If so, check my post, just above yours. You can remove all ‘duration-split-up-code’, since it’s no longer nessecary.
If you’re using the standard delay-activity, remember that when an event of any eventdriven-activities in a state occur, all eventdriven-activities are first unsubscribed. Maybe the unsubscribe-logic of the activities causes problems.
And it’s always good to configure tracing-settings for WF in your app/web.config. Just enable max. logging, and you’ll be able to troubleshoot most issues.
Robert te Kaat
Thanks for your help. I am using standard (out of the box) delay activity.
Let me try enable logging & see what information i am going to get.
But I have a question. I can surely use your help.
In my scenario, my workflow would trigger something based on a timer(delay activity) but doesn’t change the state. If events are going to be unsubscribed, do I need to subscribe again (If so , How?)as I have events that could be fired after this timer event? or Should I set the state to the same state again (would that automatically subscribe everything back again?)?
Please let me know .
Thanks in advance & looking into my issue description.
Vasu
Any eventdriven inside a State-activity MUST set the state. Either to the same state or to a different state. If you don’t, you end up with a workflow that does not accept events (from either a passed timer duration or incoming event (eg: HandleExternalEvent)) and is therefore completely useless.
And make sure the fault-/cancellation-/compensation-handler call SetState as well!
robertka