Skip to main content

Wait for external events

Durable wait for external events is a step that can be used wait for external events (like human interaction). Additionally, tasks that have previously been created with Create tasks can be awaited for.

When running in Suspend style, for each external event or task that is awaited for, the task is resumed for the batch of events/tasks that are completed at a given.

API reference:

Required scopes:

  • Data/Buildby.Taskurai/steps/create

Optional scopes:

  • Data/Buildby.Taskurai/sensitive/read: Can return sensitive data.
  • Data/Buildby.Taskurai/secrets/read: Can read global secrets.

Prerequisites

Waiting for external event (eg. human interaction)

It is possible to suspend a command to wait for an extern system to perform an action or to wait for human interaction (like approval, rejection, etc).

While waiting, the command is suspended. When the external event is received, the command is resumed.

The following sample waits for the event managerApprovalEvent to be raised. The step is configured to time-out after 2 hours, in that case a StepTimeoutException will be thrown.

The caller of the external event can pass any serializable data to the event, it is possible to specify the return type to automatically deserialize the data.

// Wait for approval of the manager
bool approval = false;
try
{
approval = await context.WaitForExternalEventAsync<bool>(
id: "wait-for-manager-approval-step",
eventName: "managerApprovalEvent",
maxDuration: 7200 // Timeout in 2 hours
);

if (!approval)
{
return Canceled(HttpStatusCode.Forbidden, $"No approval received from approver.");
}
}
catch (StepCanceledException)
{
return Canceled(HttpStatusCode.Forbidden, $"Canceled while waiting for approval from approver.");
}
catch (StepTimeoutException)
{
return Canceled(HttpStatusCode.Forbidden, $"Timeout while waiting for approval from approver.");
}

Events can be raised by any system or code that is activated by human interaction. The task id must be the id of the task waiting for the external event. The event data return can be any serializable type.

To raise an event, the Data/Buildby.Taskurai/events/create scope is required. For more information, read the Raising task events section.

// Setup Taskurai client
var taskurai = new TaskuraiClient(configuration["Taskurai:Pat"], new TaskuraiClientOptions()
{
IsolationMode = configuration.GetValue<bool>("Taskurai:IsolationMode", true)
});

var taskId = "5e7f3b48-8914-478e-8b6c-35c8d5924c7a"; // Task waiting for external events

await taskurai.RaiseTaskEventAsync(
id: taskId,
eventName: "managerApprovalEvent",
eventData: true, // Return true
cancellationToken: ct
);

Waiting for multiple external events

It is possible to wait for multiple external events (and tasks to be completed) at the same time. The following sample waits for the events managerApprovalEvent and financeApprovalEvent to be raised. The step is configured to time-out after 2 hours, in that case a StepTimeoutException will be thrown.

While waiting, the command is suspended. When the external event is received, the command is resumed. While waiting for more than one event, the command will be resumed for the batch of events that are completed at a given time. To follow up on intermediate results, use the progress or progressAsync delegate. To stop processing the step, once an event, like a refusal is received, return false in the progress delegate.

// Wait for approval of the manager
List<bool> approvals = [];
try
{
approvals = await context.WaitForExternalEventsAsync<bool>(
id: "wait-for-manager-approval-step",
eventNames: ["managerApprovalEvent", "financeApprovalEvent"],
maxDuration: 7200, // Timeout in 2 hours
progress: (response, progress) =>
{
Logger.LogInformation($"Progress waiting for event: {progress.Percentage}%, message = '{progress.Message}'.");

var externalEvents = response.Outputs.GetExternalEvents();
if (externalEvents?.Count > 0)
{
Logger.LogInformation($"External events: {string.Join(", ", externalEvents?.Select(e => string.Concat(e.EventName, " : ", e.GetEventDataValue<bool>().ToString())))}.");

var intermediateApprovals = response.Outputs.GetExternalEvents<bool>();

if ((intermediateApprovals != null) && intermediateApprovals.Any(approval => approval == false))
{
return false; // Stop processing, step wil throw a StepCanceledException
}
}
return true; // continue processing
}
);

if ((approvals == null) || (approvals.Count == 0) || approvals.Any(a => a == false))
{
return Canceled(HttpStatusCode.Forbidden, $"No approval received from approvers.");
}
}
catch (StepCanceledException)
{
return Canceled(HttpStatusCode.Forbidden, $"Canceled while waiting for approval from approver.");
}
catch (StepTimeoutException)
{
return Canceled(HttpStatusCode.Forbidden, $"Timeout while waiting for approval from approvers.");
}