Skip to Content
Evaluate Get Started

Timer

A timer fires a transition automatically when its persisted execution time becomes due. Developers use timers to model deadlines, timeouts, reminders, and scheduled tasks in their workflow processes. This page covers timer types, registration, execution, and calendar integration.

What a timer is

A timer is a named schedule defined in the scheme. Unlike a command, which requires a user or system to call ExecuteCommandAsync, a timer is processed by Workflow Runtime. The runtime calculates and stores its execution time, and the timer manager polls for due records.

A transition can have at most one timer. Multiple transitions can reference the same timer.

Each timer has a name and three configuration fields:

Timer field properties
FieldDescription
NameUnique identifier used by transitions to reference this timer via NameRef
TypeDetermines how the runtime interprets the schedule value
ValueThe schedule itself: a duration, time of day, date, or expression
NotOverrideIfExistsKeeps the execution time when a timer with the same name is already registered for the process

The five timer types are:

Timer types
Timer typeWhat it means
IntervalFire after a delay, such as "2d 5h 24m" or 3600000 milliseconds
TimeFire at a time of day (HH:mm:ss); registration selects today or tomorrow
DateFire at midnight on a specific calendar date
DateAndTimeFire at an exact date and time
ExpressionEvaluate a C# expression that returns a date, time span, or interval

How timers work in a scheme

Timers are declared in the <Timers> section of a scheme XML. Each <Timer> element defines one named schedule. Transitions reference timers through their trigger element.

The following scheme models a service desk ticket. Two timers enforce different kinds of deadlines: an Interval timer fires four hours after the ticket enters the InProgress activity (SLA breach), and an Expression timer fires at midnight on January 1st of the following year, computed dynamically.

ServiceDeskTicket.xml
<Process Name="ServiceDeskTicket">    <Timers>        <Timer Name="SLA" Type="Interval" Value="4h" NotOverrideIfExists="false"/>        <Timer Name="HardDeadline" Type="Expression"               Value="new DateTime(DateTime.Now.Year + 1, 1, 1)"/>    </Timers>    <Commands>        <Command Name="Assign"/>        <Command Name="Resolve"/>    </Commands>    <Activities>        <Activity Name="Open" IsInitial="True">            <Designer X="200" Y="50"/>        </Activity>        <Activity Name="InProgress">            <Designer X="50" Y="250"/>        </Activity>        <Activity Name="Resolved" IsFinal="True">            <Designer X="200" Y="450"/>        </Activity>        <Activity Name="Breached" IsFinal="True">            <Designer X="350" Y="250"/>        </Activity>    </Activities>    <Transitions>        <Transition Name="AssignTicket" From="Open" To="InProgress" Classifier="Direct">            <Triggers>                <Trigger Type="Command" NameRef="Assign"/>            </Triggers>            <Conditions>                <Condition Type="Always"/>            </Conditions>        </Transition>        <Transition Name="ResolveTicket" From="InProgress" To="Resolved" Classifier="Direct">            <Triggers>                <Trigger Type="Command" NameRef="Resolve"/>            </Triggers>            <Conditions>                <Condition Type="Always"/>            </Conditions>        </Transition>        <Transition Name="SLABreach" From="InProgress" To="Breached" Classifier="Direct">            <Triggers>                <Trigger Type="Timer" NameRef="SLA"/>            </Triggers>            <Conditions>                <Condition Type="Always"/>            </Conditions>        </Transition>        <Transition Name="DeadlineBreach" From="Open" To="Breached" Classifier="Direct">            <Triggers>                <Trigger Type="Timer" NameRef="HardDeadline"/>            </Triggers>            <Conditions>                <Condition Type="Always"/>            </Conditions>        </Transition>    </Transitions></Process>

ServiceDeskTicket scheme showing two timer-triggered transitions: an Interval SLA timer from InProgress to Breached, and an Expression deadline timer from Open to Breached

Two transitions are timer-triggered:

  • The DeadlineBreach transition from Open to Breached is triggered by HardDeadline. The runtime registers this timer as soon as the process instance enters the Open activity. If the ticket is still in Open when the deadline passes, the timer fires and the process moves to Breached.
  • The SLABreach transition from InProgress to Breached is triggered by SLA. The runtime registers this timer when the ticket is assigned and enters InProgress. If no one resolves the ticket within four hours, the timer fires and the process moves to Breached.

The HardDeadline timer is registered when the process first enters Open (the initial activity). If the ticket is assigned before the deadline, the Assign command moves it to InProgress. The process then clears the Open-activity timers and registers the InProgress -activity timers (SLA).

A scheme can declare any number of timers. Each timer is defined once and can be referenced by multiple transitions.

How timers connect to the runtime

A timer relates a transition to the process instance's timeline.

Timer registration

When a process instance enters an activity, WorkflowRuntime registers timers associated with outgoing timer-triggered transitions. The sequence is:

  1. Find every timer-triggered transition from the current activity.
  2. Clear previous timers, except same-name timers preserved by NotOverrideIfExists.
  3. Calculate the execution time from the timer type and process context.
  4. Register the timer in the persistence store. An overridable same-name timer is updated.

Timer execution

The runtime's TimerManager periodically queries the persistence store for due timers. For each timer, it loads the process instance and evaluates conditions on transitions from the current activity that reference the timer name:

  • If a condition passes, the transition fires and the process moves to the target activity. The new activity triggers a fresh registration cycle.
  • If no condition passes, the normal clear-and-register cycle runs for the current activity. With the default NotOverrideIfExists="false", this replaces the fired timer and calculates its execution time again.

Recovery after downtime

Timer records remain in persistence while the runtime is stopped. After WorkflowRuntime.StartAsync() starts the timer manager, a poll selects records whose execution time has passed. Single-server mode polls immediately on startup; multi-server mode begins after its configured timer interval.

Custom expression values

Workflow Runtime compiles an Expression timer against the process instance. A DateTime result is used directly. A TimeSpan, integer number of milliseconds, or interval string is added to the runtime's current time. If the result cannot be converted, the optional GetCustomTimerValueAsync delegate can supply an execution time.

An expression reads a process parameter with the @ prefix. For a DateTime parameter named Deadline, the expression (@Deadline).AddHours(1) schedules the timer one hour after that parameter value.

Register a custom resolver when an expression value uses an application-specific format:

Custom timer value resolver
using System;using System.Threading.Tasks;using OptimaJet.Workflow.Core.Runtime;public static class TimerConfiguration{    public static void Configure(WorkflowRuntime runtime)    {        runtime.GetCustomTimerValueAsync = (value, timerName) =>        {            if (timerName == "EndOfBusinessDay")            {                return Task.FromResult<DateTime?>(                    DateTime.Today.AddHours(18));            }            return Task.FromResult<DateTime?>(null);        };    }}

Hosting environment considerations

Call WorkflowRuntime.StartAsync() after configuration to start the timer manager. See Configure Timers for hosting guidance and Runtime Settings for single-server and multi-server setup.

Calendar-aware timers

An Interval timer can use the work calendar resolved for the process instance. A value such as "8 whours" is converted to elapsed time according to that calendar's workdays and working hours.

When timers matter

  • SLA enforcement: escalate a ticket if nobody reviews it within 24 hours
  • Deadline: reject an approval if not decided by end of day
  • Reminder: trigger a notification path after a delay
  • Scheduled transition: move a waiting process at a configured time

See also

Frequently asked questions

What timer types does Workflow Engine support?

Five types: Interval (delay), Time (time of day), Date (calendar date at midnight), DateAndTime (exact moment), and Expression (a C# expression that resolves an execution time).

Can a timer coexist with a command on the same transition?

No. A transition can have exactly one trigger: either a command or a timer. If you need both user-triggered and time-triggered paths from the same activity, create two transitions.

What happens when a timer fires but conditions on the transition fail?

With the default NotOverrideIfExists="false", the runtime clears and registers the current activity's timers again. Interval starts a new delay from the current time, Time selects today or tomorrow again, Expression is reevaluated, and Date or DateAndTime resolves to the same configured moment. With NotOverrideIfExists="true", the fired same-name timer is not replaced and is deleted after the execution attempt.

What is the NotOverrideIfExists property for?

When NotOverrideIfExists is true, the runtime keeps an existing timer with the same name when that timer still applies after an activity change. This prevents an active countdown from restarting when the process loops back or moves to another activity that uses the same timer.

What happens to timers after the hosting environment restarts?

Persisted timers that became due while Workflow Runtime was stopped are selected after the timer manager starts polling. Single-server mode performs its first poll immediately; multi-server mode waits for its configured timer interval.