Activity "Lifetime" Methods

QUESTION: How do I manage resources (like sockets) which are held by an activity?

ANSWER: System.Workflow.ComponentModel.Activity has several overridable methods defined, but there is a newer subset of these methods which I like to refer to as "Lifetime" methods. These are methods that bookend the activity's life from some point of view. They are:

OnActivityExecutionContextLoad / OnActivityExecutionContextUnload / Dispose

Initialize (also a signal method) / Uninitialize

Short Version

The short version is that OnActivityExecutionContextLoad and OnActivityExecutionContextUnload bookend the activity's memory lifetime as far as the Windows Workflow Foundation (WF) runtime is concerned. OnActivityExecutionContextLoad will be called just after an activity is loaded into the runtime's memory and OnActivityExecutionContextUnload will be called just before an activity is persisted to the database. 

OnActivityExecutionContextLoad also has a component of being paired with Dispose to represent the activity's memory lifetime as it applies to the CLR. Activities which run almost never have their constructors called directly but instead these activities are deserialized from a stream. That means that OnActivityContextLoad is really the only safe place to do object creation time resource allocation. Dispose is called before an object is released for garbage collection by the runtime and could be used for NonSerialized activity fields which need to be closed, disposed, or cleaned up in any other way.

Initialize and Uninitialize are the markers for an activity's execution lifetime. For the rest of this paragraph any reference to an "activity instance" will have NOTHING to do with its CLR lifetime. Since workflows are unloaded and loaded numerous times during execution a single "activity instance" could actually have been executed through the use of dozens of CLR object instances of the activity. That said, before an "activity instance" will take part in any of the protocol signals it will be initialized; this includes the initialization of cloned activities which execute in new contexts. Once an "activity instance" is done taking part in the protocol it will have Uninitialize called.

Long Version

Before an activity does anything (IE – when it is loaded into memory within the runtime’s scope) OnActivityExecutionContextLoad will be called. Initialize is called on every activity in the workflow tree at workflow Create time. Initialize is also called for every clone in a context spawning activity when the clone is spawned. You can tell that you are a cloned activity because IsDynamicActivity will be set to true.

OnActivityExecutionContextUnload is called before an activity is saved to the database. This means that if the workflow is unloading then this method will be called … additionally if a context is being saved for future compensation then this method will be called on all contained activities. Note that PersistOnClose does not cause OnActivityExecutionContextUnload to be called for the workflow as a whole because the workflow object is not being jettisoned from memory.

Dispose is called when we are done with the .NET object representing the activity. So, in the workflow unload case, we will call OnActivityExecutionContextUnload immediately followed by Dispose. In the case of PersistOnClose we will NOT call Dispose on any activities. Only when the activity is being removed from memory will Dispose be called.

Uninitialize has two cases. 1) The activity does not have any compensation responsibilities. 2) The activity has to remain “valid” for compensation. Essentially, Uninitialize is the WF approximation of .NETs Dispose … when we think an activity will never be called upon to take part in the activity protocol again then we will Uninitialize it. This shows up as a new flag on ActivityExecutionResult.

For activities which fall in category (1) Uninitialize is called immediately after the activity closes. Any primitive activity which does not implement ICompensatableActivity has this behavior. Additionally, any composite activity which has NO children, grandchildren, great grandchildren, etc. which implement ICompensatable will be uninitialized immediately as well. However, if an activity or any of its children or sub children implement ICompensatableActivity then the activity will NOT be Uninitialized until the compensatable activity has either been compensated or has been determined that it cannot ever be compensated (the workflow terminates or completes). For example, if I have the following:

While
            Sequence
                        Code
                        MyCompensatableActivity

Code will uninitialize immediately. MyCompensatableActivity, Sequence, and While will NOT be able to uninitialize until either MyCompensatable activity compensates or the workflow completes and disposes of the compensatable activity.

Finally, the reason for the caveat that Uninitialize is an “approximation” of .NET’s Dispose is because you can execute disposed activities again. It is possible to “rerun” an activity which has executed and closed in another context. Using a Page Flow example, imagine that you have an activity which represents Page3. The user enters data into Page3 which you save as fields on your activity. They then proceed to Page4 causing WF to close Page3 and then call Uninitialize. It is possible to model the user pressing the Back button by simply creating a new context and passing the closed Page3 object from the old context as the template activity. This will cause the Page3 object to have Initialize called again, but all of the data will be intact just as it was left at the return of the previous Uninitialize call.

Hope that helps clear up some of the hooks for acquiring and releasing resources.