Subprocesses/Parallel
Introduction
A subprocess is a process that runs together with a regular process. When creating a transition in the designer, you can specify whether it starts or finishes a subprocess.
To create a subprocess, one transition starting the subprocess is enough. As soon as the engine reaches this transition, it will be executed and the subprocess will be created.
Workflow Engine supports subprocesses of any level, i.e. a subprocess can create subprocesses of its own. That is to say, if, executing a subprocess, the engine reaches an activity that should start another subprocess, then it will create a sub-subprocess.
The hierarchy of subprocesses is a tree.
First of all, let us define two terms related to the hierarchy of subprocesses to be used further:
- Root Process - is the root of the entire hierarchy; this process behaves exactly the same as a regular process.
- Parent Process - is the process that directly creates the current sub-process. For the subprocesses of the first level, the parent process is identical to the root process.
The lifecycle of a subprocess slightly differs from the lifecycle of a regular process in finalizing the subprocess and updating its scheme. We'll discuss these issues further in this section.
Application of Subprocesses
Subprocesses can be used in many cases. For example:
- logical parallelism, for instance, when the process of document approval is parallel for several users, and then, the parallel branches are merged into the root process.
- physical parallelism that allows you to run subprocesses in separate threads.
- launching a third-party task without changing the state of the root process.
- creating a command available from any of the root process states, after creating a subprocess. Sometimes this solution can dramatically simplify the scheme.
- management of the root process state from the subprocesses.
Starting a Subprocess
As noted earlier, a subprocess is started by a transition with the corresponding specification. First of all, you should pay attention to the additional launch settings in the default mode and in the expert mode:
You should also take note that a transition able to start a subprocess has a choice of triggers (commands and timers) and conditions to perform the transit; therefore, you can create a subprocess by command, by timer and by setting the conditions for creation.
Let us consider the settings to start a subprocess:
-
Subprocess startup type - the type of the subprocess startup. The following three options are available: Same Thread, Another Thread and Timer Queue.
- Same Thread - in this case, the subprocess is launched and executed in the same thread as the parent process.
The activities of the subprocess and the parent process are executed sequentially. First, the parent process is
performed until the transition in it starts the subprocess; then, all of the subprocess activities are executed
until it ends; at last, the remaining activities of the parent process are done. See the following figure.
- Another Thread - in this case, the subprocess is launched and executed in another thread available from the thread pool, different from the thread of the parent process. The activities of the subprocess and the parent process are executed in parallel. First, the parent process is performed until the transition in it starts the subprocess; then, the activities of the subprocess and the parent process are executed in parallel until they are ended. See the following figure.
- Timer Queue - in this case, the sequence of launching and executing the subprocess is similar to Another Thread, but the subprocess is not executed in a separate thread, instead, a timer with a zero response interval is used. What is this mode for? For example, if you apply the multi-server mode with your Workflow Engine instances using one processor core (which can happen, when deploying in the cloud), then multiple threads will not give anything but slow down the system. Thus, if you want to get parallel execution, you should choose Timer Queue. Also, this mode can be used to transfer execution to other instances of the Workflow Engine (or the Workflow Server).
- Same Thread - in this case, the subprocess is launched and executed in the same thread as the parent process.
The activities of the subprocess and the parent process are executed sequentially. First, the parent process is
performed until the transition in it starts the subprocess; then, all of the subprocess activities are executed
until it ends; at last, the remaining activities of the parent process are done. See the following figure.
-
Subprocess name - here you can specify the subprocess name or write an expression that computes the subprocess name from the process parameters. If the subprocess name is not specified, then it will be the same as the transition name. What is the subprocess name for? Each child of the parent process must have a unique name; to create a second subprocess with the same name is impossible.
In practical terms, if your subprocess is created by a command and the subprocess name is fixed, then, after the first execution of the command and the subprocess creation, the command will become unavailable. This way you can create only one subprocess.
However, if the subprocess name is computed from the expression you write in the Subprocess Name field, then, changing the process parameters, you can create as many identical subprocesses as you want.
In the example above, the process name is defined by the expression
@SomeParameter1 + "_" + @SomeParameter2
, thus, by changing one or both of the process parameters, you can create subprocesses with different names that will look like "Parameter1Value_Parameter2Value". The expression that defines the subprocess name should return a string. Here you can use the full power of the process parameters. A process parameter reference begins with the '@' character. To substitute parameter values into the expression, use the following syntax:@ParameterName
- the easiest option, the value of the parameter named 'parameterName' is substituted from the process. The type is the same as the type the parameter value.@ObjectParameter.ObjectProperty.StringProperty
- partial parameters can be used in the expression. That is, if you have a complex, multi-level object stored in the parameter, you can substitute a part of this object.@ParameterName:formatString
- the expression with a format string. A string formatted according to theformatString
template is substituted using the format syntax for the .NET platform. Please, for more details see here.@(ParameterName:formatString)
- if different special characters and delimiters are used in the format string, you must additionally enclose the expression in round brackets.
In the expression, any other elements of lambda expressions in C# can be used, see here for more details. For a deeper understanding, let us consider how the Workflow Engine interprets the following expression:
@SomeParameter1 + "_" + @SomeParameter2
this expression will turn into an asynchronous lambda that returns a string with the new name of the subprocess:
async (processInstance) => (await processInstance.GetParameterAsync<dynamic> ("SomeParameter1")) + "_" + (await processInstance.GetParameterAsync<dynamic> ("SomeParameter2"));
In the examples above, we describe subprocesses created by a command, but the subprocess name works just the same for subprocesses created by automatic transitions.
-
Subprocess Id - since a subprocess is also a process, it always has a unique identifier
ProcessId
. By default, it is always generated by callingGuid.NewGuid()
; in the vast majority of cases, there is no need to change this behavior. However, in this field you can write the parameter name as "@SomeParameterContainingGuid" and this value of the parameter will be used as the process ID. It's important to note that the process parameter must either be a Guid or contain a string representing a Guid value. -
Parameters copy strategy - when a subprocess is created, it gets a copy of all of the parent process parameters; that is the default behavior. This behavior can be changed using the Parameters copy strategy setting. The following options are available.
- Copy All - the subprocess receives a copy of all of the parent process parameters.
- Ignore All - the subprocess does not receive any of the parent process parameters.
- Copy specified - the subprocess receives only the explicitly specified (comma-separated) parameters of the parent process.
- Ignore specified - the subprocess receives all but the explicitly specified (comma-separated) parameters of the parent process.
-
Disable parent process control - disabling the parent process control over the subprocess existence. When the Workflow Engine creates a subprocess, it evaluates the parent process states that allow this subprocess to exist. If the parent process reaches a state that prohibits the subprocess existence, this subprocess is automatically deleted. That is the default behavior. You can disable this mechanism by setting the Disable parent process control checkbox. How are the parent process states that control the subprocess existence evaluated? Here is the algorithm description:
The algorithm starts at an activity with the transition launching a subprocess, and inspects any activity that can be reached from this point by transitions with Classifier = Direct or Unspecified. The inspection is terminated on the activity with the transition finalizing this subprocess (you can read about finalizing of subprocesses below).
Since this algorithm can seem confusing, we illustrate it with the following examples:
As already mentioned above, a subprocess can be created by a transition with Auto, Command or Timer triggers; moreover, this transition can be conditional. Let us summarize how everything works:
-
If the transition starting a subprocess has the Auto trigger, then the subprocess will be created after the parent process reaches the activity with this transition.
-
If the transition starting a subprocess has the Command trigger, then the subprocess will be created by a command.
-
If the transition starting a subprocess has the Timer trigger, then the subprocess will be created when this timer expires.
-
If the transition is unconditional, i.e. Condition = Always, then subprocesses will be created regardless of the number of transitions outgoing from the activity.
-
If the transition is conditional, i.e. Condition = Conditional or Otherwise, then at most one subprocess will be created, regardless of the number of transitions outgoing from the activity. This way you can create different subprocesses depending on different conditions. See the explanatory figure below:
Finalizing a Subprocess
A subprocess can either work independently of its parent process or merge into it. At first, let us consider the two simplest cases, when a subprocess works independently of its parent process.
In this case, if the subprocess reaches the state marked as final, it will be deleted automatically. If there is no final state in the scheme, then the subprocess will not be deleted automatically.
The subprocess can be also merged into the parent. In this case, the subprocess behavior is determined by the output transition. Here are the available settings.
-
Merge subprocess via set state - sets the merge mode. There are two merge modes.
-
Merge with the transition to be computed. In this mode, the Merge subprocess via set state checkbox should be unchecked. What happens in this mode?
- The subprocess is finalized, and the Workflow Engine tries to execute any transition with the Auto trigger, which comes from the activity where the transition finalizing the subprocess leads.
- If the transition is conditional, the conditions are evaluated.
- If the transition is unconditional or the conditions return 'true', the transition is performed in the parent process.
- Afterwards, the subprocess is deleted.
In practical terms, this means that you can manage the merging of subprocesses and the parent process. For example, this can be useful for the parallel document's approval (logical parallelism) or for physical parallelism, if necessary. 90% of the required merge cases are covered by the predefined condition of the Basic Plugin called CheckAllSubprocessesCompleted.
This condition has two options of work:
- AllSubprocesses - returns 'true' if all of the subprocesses have been finalized. That is, we are waiting for the subprocesses only.
- AllSubprocessesAndParent - returns 'true' if all of the subprocesses have been finalized and the parent process is in the state with the outgoing transition with this condition. That is, we are waiting for both the subprocesses and the parent process.
-
Merge with the state. In this mode, the Merge subprocess via set state checkbox must be checked. Then, the following happens:
- The subprocess is finalized and the parent process is forcibly set to the state that the transition finalizing the subprocess has reached.
- The subprocess is deleted.
In practical terms, this means that you can create a command that will be available from any state of the parent process as long as the subprocess exists. For example, this command can rollback the parent process to its initial state. Moreover, other cases of managing the parent process are also feasible.
-
-
Parameters merge strategy - when the subprocess is merged into the parent process, all of its parameters that are absent (or equal to null) in the parent process will be transferred to this parent process. This default behavior can be changed using this setting. The following options are available:
- Overwrite All Nulls - only those parameters that are absent (or equal to null) in the parent process will be transferred from the subprocess to the parent process. The parameters are mapped by name.
- Overwrite All - all of the subprocess parameters will be transferred to the parent process; those parameters that already exist in the parent process will be overwritten.
- Overwrite Specified - only the explicitly specified (comma-separated) parameters of the subprocess will be transferred to the parent process; those parameters that exist both in the parent process and in the subprocess will be overwritten.
- Dont Overwrite Specified - all of the subprocess parameters but the explicitly specified (comma-separated) ones will be transferred to the parent process; those parameters that exist both in the parent process and in the subprocess will be overwritten.
When merging, the parameters are always mapped by name.
Limitations of Subprocess Creation
You can create subprocesses of any level, but if a subprocess is merged into the parent process, then such a transition must be marked as finalizing the subprocess. In the example that is shown in the figure below, the Workflow Engine will not be able to figure out if the activity belongs to a subprocess or the parent process.
Therefore, it is very important to correctly label the transitions that start and finalize subprocesses. With the correct marking, even the following case becomes possible:
Programmatic Access to Subprocess Tree
Sometimes, to get the access to all of the currently available subprocess identifiers, the first thing you need is to
reach the subprocess tree. For this purpose, the runtime.GetProcessInstancesTree
method or its asynchronous
version runtime.GetProcessInstancesTreeAsync
is used. The code below performs the console output of the subprocess
tree.
private static void WriteProcessTree(Guid rootProcessId)
{
var processTree = WorkflowInit.Runtime.GetProcessInstancesTree(rootProcessId);
if (processTree != null)
{
var current = processTree.Root;
var level = "->";
WriteSubprocesses(current, level);
}
}
private static void WriteSubprocesses(ProcessInstancesTree current, string level)
{
foreach (var child in current.Children)
{
Console.WriteLine("{0}SubProcessId = '{1}'. CurrentState: {2}, CurrentActivity: {3}",
level,
child.Id,
WorkflowInit.Runtime.GetCurrentStateName(child.Id),
WorkflowInit.Runtime.GetCurrentActivityName(child.Id));
WriteSubprocesses(child, level + "->");
}
}
The runtime.GetProcessInstancesTree
method takes as input either the root process identifier or the ProcessInstance
object and returns the tree root as an instance of the ProcessInstancesTree
class. Each instance of this class
contains the following members:
Id
- the process Id.Name
- the subprocess name.StartingTransitionName
- the name of the transition starting the subprocess.Parent
- the parent node of the tree, also an instance of theProcessInstancesTree
class.Root
- the root node of the tree, also an instance of theProcessInstancesTree
class.IsRoot
- returns true if the current node is the root node.Children
- the list of the child nodes for the current node.
Thus, having at your disposal the root object of the ProcessInstancesTree
class, you can receive any information about
the subprocess tree.
Getting the List of Commands and Executing Commands with Subprocesses
As you know, to get the list of commands and execute it, the following code is used in the Workflow Engine. Keep in mind, that the Workflow Engine has a synchronous counterpart for every asynchronous method.
IEnumerable<WorkflowCommand> availableCommands = await runtime.GetAvailableCommandsAsync(processId, identityId);
WorkflowCommand selectedCommand = availableCommands.FirstOrDefault(c => c.CommandName == "CommandName");
CommandExecutionResult result = await runtime.ExecuteCommandAsync(selectedCommand, identityId, identityId);
How do you get commands for all of the subprocesses? The good news is that you don't need to get the subprocess tree and
pull the commands out of each of the subprocesses. The runtime.GetAvailableCommands
and runtime.GetAvailableCommandsAsync
methods return commands for the current process (determined by processId) and
all of its subprocesses. Thus, for most use cases, it is enough to know only the Root Process Id.
The WorkflowCommand
object has the following properties that help to understand which subprocess the command is
related to.
WorkflowCommand command = ...;
Guid processId = command.ProcessId;
bool isSubprocessCommand = command.IsForSubprocess;
Utility Properties of the ProcessInstance class
An object of the ProcessInstance
type contains the properties that return the Ids of the root process and the parent
process.
ProcessInstance processInstance = ...;
Guid processId = processInstance.ProcessId;
Guid rootProcessId = processInstance.RootProcessId;
Guid? parentProcessId = processInstance.ParentProcessId;
bool isSubprocess = processInstance.IsSubprocess;
Also, when the subprocess is merged into the main process, then all of the subprocess parameters will be available (but only at the merge time) in the special collection:
ProcessInstance processInstance = ...;
ParametersCollection mergedParameters = processInstance.MergedSubprocessParameters;
Utility Methods for Merge with Computing of Transition
In the case, when a subprocess is merged with computing the transition, two utility methods are available. They are similar to those connected via the Basic Plugin that we have considered above.
bool completed = await runtime.CheckAllSubprocessesCompletedAsync(processInstance);
bool completed = await runtime.CheckAllSubprocessesAndParentProcessCompletedAsync(processInstance);
Getting the List of Users Able to Execute a Command with Subprocesses
The last quite useful function allows to retrieve the list of User IDs, who may execute a command in the root process from a subprocess. It is necessary when a subprocess contains a command (for example, a rejection) that may be executed by those users who may execute this command in the root process.
var filter = new TreeSearchFilter(subProcessId)
{
Include = TreeSearchInclude.OnlyStartPoint,
StartFrom = TreeSearchStart.FromRoot
};
IEnumerable<string> identityIds = WorkflowInit.Runtime.GetAllActorsForCommandTransitions(filter);
TreeSearchFilter
contains the conditions for starting and traversing the subprocess tree to get the list of users who
may execute the command. This method is most frequently used when working with the inbox.
- The process or subprocess identifier known to us is passed to the
new TreeSearchFilter(subProcessId)
constructor. StartFrom
- sets the starting point in the subprocess tree, from which to start traversing the tree and getting the list of users. The possible values are:TreeSearchStart.FromRoot
- the traversal starts at the tree root.TreeSearchStart.FromMe
- the traversal starts at the current node, that is the process, whose ID is specified in the constructor.TreeSearchStart.FromParent
- the traversal starts at the parent node (or process), whose ID is specified in the constructor.
Include
- specifies the direction of traversing the subprocess tree. The possible values are:AllDownIncludeStartPoint
- all of the child nodes of the start node (up to the last level of the tree), the start node included.AllUpIncludeStartPoint
- all of the parent nodes of the start node (down to the root), the start node included.OnlyStartPoint
- the start node only.AllDownExcludeStartPoint
- all of the child nodes of the start node (up to the last level of the tree), the start node excluded.AllUpExcludeStartPoint
- all of the parent nodes of the start node (down to the root), the start node excluded.
Setting Root Process Parameters from a Subprocess
It is possible to safely set and get the root process parameters from a subprocess, using the following methods of an
object of the ProcessInstance
type.
Getting a parameter:
ProcessInstance subprocessInstance = ...
var parameterValue = subprocessInstance.GetRootPersistedParameter<T>("parameter name");
var parameterValue = await subprocessInstance.GetRootPersistedParameterAsync<T>("parameter name");
Setting a parameter:
ProcessInstance subprocessInstance = ...
subprocessInstance.SetRootPersistenceParameter("parameter name", value);
await subprocessInstance.SetRootPersistenceParameterAsync("parameter name", value);
Besides, it is recommended to use these methods to work with the parameters of the root process, since they work correctly with the process locks and ensure safe setting of the root process parameters from a subprocess.
The Basic Plugin adds exactly the same actions.
Update of Subprocess Scheme
Updating of subprocess schemes is identical to updating of regular process schemes, including automatic updates, event calls, etc., aside from the several points:
-
A subprocess scheme cannot be updated directly; it is updated during the update of the root process scheme.
-
During the subprocess scheme update, the name of the subprocess starting transition is used. If a transition with this name remained in the updated scheme, the scheme is divided into subprocesses, and the subprocess scheme gets updated. If there is no transition with such a name in the updated scheme, the
OnStartingTransitionNotFound
is called. You may choose from the three options available in the transition not found handler:-
Decide to remove the subprocess:
runtime.OnStartingTransitionNotFound += (sender, args) =>
{
args.DropProcess();
}; -
Decide that the subprocess (and its scheme) should start with a transition with another name:
runtime.OnStartingTransitionNotFound += (sender, args) =>
{
args.StartWithNewTransition("new transition name");
}; -
Ignore this situation, skipping the subprocess scheme update:
runtime.OnStartingTransitionNotFound += (sender, args) =>
{
args.Ignore();
};
If you don’t change the subprocess behavior in the handler, the subprocess will be deleted.
-
Additional Information
In order to learn how to create parallel approval within a single stage, refer to this section of the documentation. Have a look at these articles to get acquainted with parallel branching examples: Parallel branching in Workflow Engine and Parallel branches. Continued.