Subprocesses

There is a possibility to create parallel branches (branch/fork) in Workflow Engine. Each parallel branch creates a subprocess. Subprocess represents an almost independent process, though its lifecycle differs in terms of finalization and scheme updates. Workflow Engine supports unlimited levels of subprocess hierarchy; in other words, each subprocess may have its own subprocesses. Thus, a subprocess hierarchy represents a tree. It is important to be aware of the two processes in terms of subprocesses:

  • RootProcess is the major process in the hierarchy. It operates in the same way as a process without subprocesses. When we create a scheme in the designer, it fully belongs to the subprocess.
  • ParentProcess is a process that creates a subprocess. It may equal to the RootProcess in case a subprocess is created by the main process. Besides, you should know that the root process and subprocesses are declared in the same scheme.

Subprocess boundaries

A subprocess should have one input transition and any number of output transitions (including 0). Both input and output transitions are marked by setting the Is Fork = true property. In case a scheme contains a Fork transition, it will be divided into subprocesses after it is uploaded to scheme cache. Besides, input and output transitions for a subprocess will be defined.

Let’s first consider the examples of how Workflow Engine divides a scheme into subprocesses.

Regular scheme

This scheme comprises two processes: the main (Root) process containing Activity RootInitial, Root1 - Root4, and a subprocess containing Activity SubIntitial - Sub1 and Root3.

Subprocess scheme

The Root1 - SubInitial1 transition will be an input one for the subprocess. During its execution, the current Activity of the basic process won’t be changed; instead, a subprocess will be created.

Activity SubInitial is the initial activity for the subprocess. Its essence is the same as the one of the initial activity operates for a regular basic process.

Activity Root3 will be the final Activity for the subprocess and at the same time a regular Activity for the root process. In this case, it determines the point where the subprocess will merge with the root process. The Implementation section of the Root3 activity will be executed only in the root process. Generally speaking, this Activity is a marker for the subprocess. The subprocess will be removed after a merger with the root process.

Initial scheme

This scheme comprises three processes. The main (Root) process contains Activity RootInitial, Root1 and Root2. The first subprocess contains Activity SubIntitial and Sub1. The second subprocess is a child subprocess of the first one. It contains Activity SubSubInitial and SubSub1. The order of scheme division is as follows:

First and second subprocesses

Second subprocess

The following point should be taken into account. The final Activity of the first subprocess is not marked as IsFinal. It means that when the subprocess is completed, it won’t be automatically deleted. It may be deleted either during transitions in the root process, or manually. The final Activity of the second subprocess is marked as IsFinal; the second subprocess will be automatically deleted when it reaches this Activity. Neither the first subprocess nor the second one have output Activities.

Incorrect scheme

This scheme is incorrect. Workflow Engine will never be able to identify which process Activity Root2 belongs to. In case a subprocess reaches the root process, such a transition should be explicitly marked as IsFork.

The algorithm for dividing schemes into subprocesses can be simply described as a combination of three rules:

  • All Activities that can be reached via the ordinary IsFork = false transitions from the InitialActivity belong to the Root process.
  • In case IsFork=true transition is output from the Activity that belongs to the Root process, it will be an input one.
  • In case IsFork=true transition is input into the Activity that belongs to the Root process, it will be an output one.

Replace “Root” with “Parent” in the above sentences, and you will get the rules for subprocesses of any level.

The algorithm for scheme division is as follows:

  • The Level = 0 value is set for the InitialActivity. Level = 0 means that Activity belongs the Root process. For the rest of the Activities, the Activity graph traversal takes place, starting with the initial Activity, to identify their level.
  • The Level value is calculated for the final Activity of the LevelTo transition. The Level value for the initial Activity – LevelFrom – is always known. In case the transition property of the IsFork = false, then LevelTo = LevelFrom. In case IsFork = true, then LevelTo = LevelFrom + 1. In other words, we assume by default that the Fork transition starts a subprocess.
  • In case the Level value of the final Activity is not identified, then it equals LevelTo. Everything becomes more complicated when the Level value for the final Activity is identified. In other words, more than one transition leads to the final Activity.
  • In case the Level value of the final Activity is identified, and its modulo differs from the calculated LevelTo by 1, it is considered a scheme layout error. Physically, it means that a subprocess of the N level is trying to complete itself into the N+1 or N-1 level process without declaring the Fork of the transition. This is unacceptable.
  • In case the Level value of the final Activity is identified and it is <= LevelTo, the Level value of the final Activity will not be changed. Physically, it means either continuation of the process, or output from the subprocess.
  • In case the Level value of the final Activity is identified and it is > LevelTo, the Level value of the final Activity is changed. Physically, it means either continuation of the process, or creation of a new subprocess.

After that, a transition will be considered:

  • regular if LevelFrom = LevelTo;
  • input to a subprocess if LevelFrom < LevelTo;
  • output from a subprocess if LevelFrom > LevelTo.

Lifecycle of a sub process

The lifecycle of a subprocess is similar to a regular process’ one, aside from its launch and finalization. Besides, a subprocess may be deleted automatically upon changing of the parent process’ state.

Lifecycle of a subprocess

Creation of a sub process

A subprocess is created by the parent process when it reaches the transition input to the subprocess. The creation method depends on the Trigger and Condition property of this transition.

In case the input transition has Trigger=Auto, a subprocess will be created automatically when the parent process reaches the Activity which this transition is output for. In case the input transition has Trigger=Command, a subprocess will be created by a command. In case the input transition has Trigger=Timer, a subprocess will be created by a timer.

The following rules apply to conditions. In case Condition = Always, subprocesses will always be created regardless of the number of input transitions outgoing from the Activity. In case Condition = Action, Otherwise, maximum one subprocess will be created,depending on conditions.

Thus, subprocesses may be created by a command, a timer, and depending on the conditions.

During the creation of a subprocess, the following events occur:

  • A decision is made whether a subprocess will be created or not, depending on a trigger and the conditions of the input transition.
  • All parent process parameters different from null are conveyed to the created subprocess.
  • Process id is put into a tree of subprocesses, which is stored in the WorkflowGlobalParameter table (object). Besides, the name of the transition that created the subprocess is saved. This is necessary in order to control the creation of subprocesses (each transition should create only one subprocess), and update of subprocess schemes.
  • Further, a process is created, initialized and executed in the way a regular process is (refer to the Lifecycle of a regular process).

Completion of a subprocess

A subprocess may be automatically deleted regardless of its completion in the following cases:

  • Its parent process was deleted. The existence of subprocesses does not make sense without their parent processes.
  • The parent process reached the state in which a subprocess can't exist.

Let’s figure out what the last sentence means and how the parent process’ state affects the existence of a subprocess. First of all, this control may be disabled by setting the Disable parent process control property = true for the input transition. In this case, a subprocess will be automatically deleted only if it reaches the Final activity or its parent process is removed. In case Disable parent process control = false, the following rule is applied:

  • A subprocess exists if its parent process is in the Activity which the input transition starts from, or it is in any of the Activities that can be reached from this Activity via transitions with Classifier = Director or Unspecified. The traversal breaks when it reaches the Activity which the input transition comes to from a subprocess if the latter exists.

For instance, let’s consider the first scheme in this section. Here, a subprocess will exist only when the root process is in the Root1, Root2 or Root3 activity. In case the root process reaches another Activity or is set to another Activity via SetState, the subprocess will be automatically deleted.

As for the second scheme, a subprocess will exist when the root process is in the Root1 or Root2 Activity.

When a subprocess reaches its final Activity, there are three possible options:

  • The final Activity does not belong to the parent process and IsFinal = false. In this case, the subprocess is left in the system.

  • The final Activity does not belong to the parent process and IsFinal = true. In this case, the subprocess is automatically deleted.

  • The final Activity belongs to the parent process. You may observe this case in the first scheme. Here, the Root3 Activity belongs both to the parent process and the subprocess (as a marker). In this case, a subprocess is merged with the parent process.

There are two merger options. They are specified by the Merge subprocess via set state attribute in the transition that is output from a subprocess.

  • Merge subprocess via set state = false. The merging algorithm is as follows:
    • The sub process copies all its parameters to the parent process.
    • Workflow Engine selects all transitions with Trigger=Auto which are outgoing from the Activity that the subprocess has come to, or, in other words, their common Activity. If we talk about the first scheme, this will be Root3. Further, WorkflowRuntime calculates the conditions of these transitions, and in case a transition condition returns true, the transition process from this transition will start in the parent process. In case none of the conditions are true, the parent process will not change its state.
    • The subprocess is deleted.
  • Merge subprocess via set state = false. The merging algorithm is as follows:
    • The subprocess copies all its parameters to the parent process.
    • Workflow Engine forcefully sets the Activity which the subprocess has come to (Root3 in the first scheme) as the current Activity of the parent process, and starts a transition process if possible. Practically, the feature similar to SetState or SetActivity is called in Workflow Engine. The major difference of this merger method from the previous is that here the state of the parent process will be changed forcefully.
  • The sub process is deleted.

You may also change the current Activity of the parent or root process manually from a subprocess, using the following code.

var root = runtime.GetProcessInstanceAndFillProcessParameters(processInstance.RootProcessId); //It is possible to use processInstance.ParentProcessId
var someActivity = root.ProcessScheme.FindActivity("SomeActivity");
runtime.SetActivityWithoutExecution(someActivity,root,true);

Subprocess scheme update

Updating subprocess schemes is identical to updating regular processes’ schemes, including automatic updates, event calls, etc., aside from the several points.

  • A subprocess scheme cannot be updated directly; subprocess schemes get updated during the update of the root process scheme.
  • During the subprocess scheme update, the name of the subprocess' input transition is used. In case the transition with such name remained in the updated scheme, the scheme is divided into subprocesses, and thesub process scheme gets updated. In case there is no transition with such name in the updated scheme, the OnStartingTransitionNotFound is called. You may choose from the three options available in the transition's handler:
  • Make a decision to remove a subprocess:
    runtime.OnStartingTransitionNotFound += (sender, args) =>
    {
        args.DropProcess();
    };
  • Decide that a subprocess (and its scheme) should begin 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 decision on subprocess behavior in the handler, the subprocess is deleted.

Subprocess functions

In most cases, it is enough to know the Id of the root process to work with subprocesses. For instance, upon receiving the list of commands, using the GetAvailableCommands method with the Id of the root process, you will get both commands of the root process and the ones of its subprocesses. In case you convey the received command to the ExecuteCommand method, it will be executed in a subprocess. The WorkflowCommand object possesses the following properties related to subprocesses:

WorkflowCommand command = ...;
Guid processId = command.ProcessId;
bool isSubprocessCommand = command.IsForSubprocess;

The command.ProcessId property contains the Id of a subprocess, not the root process, even if you called the command, using the id of the root process. 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;

Besides, ProcessInstance contains the collection of all parameters of the subprocess that is merged with the parent process.

ProcessInstance processInstance = ...;
ParametersCollection mergedParametrs = processInstance.MergedSubprocessParameters;

You may also gain access to the tree of subprocesses. For instance, in the following example, it originates in the root and contains information about all existing subprocesses for the root process.

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 last quite useful function allows to retreive the list of Ids of users, who may execute a command in the root process, from a subprocess. It is necessary when a subprocess contains a command (for instance, a rejection) that may be executed by those users who may execute a command in the root process.

var filter = new TreeSearchFilter(subProcessId)
{
    Include = TreeSearchInclude.OnlyStartPoint,
    StartFrom = TreeSearchStart.FromRoot
};
IEnumerable<string> identityIds = WorkflowInit.Runtime.GetAllActorsForCommandTransitions(filter);
Top