Handling external events in Durable Functions (Azure Functions)
Orchestrator functions have the ability to wait and listen for external events. This feature of Durable Functions is often useful for handling human interaction or other external triggers.
Note
External events are one-way asynchronous operations. They are not suitable for situations where the client sending the event needs a synchronous response from the orchestrator function.
Wait for events
The "wait-for-external-event" API of the orchestration trigger binding allows an orchestrator function to asynchronously wait and listen for an event delivered by an external client. The listening orchestrator function declares the name of the event and the shape of the data it expects to receive.
[FunctionName("BudgetApproval")]
public static async Task Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
bool approved = await context.WaitForExternalEvent<bool>("Approval");
if (approved)
{
// approval granted - do the approved action
}
else
{
// approval denied - send a notification
}
}
Note
The previous C# code is for Durable Functions 2.x. For Durable Functions 1.x, you must use DurableOrchestrationContext
instead of IDurableOrchestrationContext
. For more information about the differences between versions, see the Durable Functions versions article.
The preceding example listens for a specific single event and takes action when it's received.
You can listen for multiple events concurrently, like in the following example, which waits for one of three possible event notifications.
[FunctionName("Select")]
public static async Task Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
var event1 = context.WaitForExternalEvent<float>("Event1");
var event2 = context.WaitForExternalEvent<bool>("Event2");
var event3 = context.WaitForExternalEvent<int>("Event3");
var winner = await Task.WhenAny(event1, event2, event3);
if (winner == event1)
{
// ...
}
else if (winner == event2)
{
// ...
}
else if (winner == event3)
{
// ...
}
}
Note
The previous C# code is for Durable Functions 2.x. For Durable Functions 1.x, you must use DurableOrchestrationContext
instead of IDurableOrchestrationContext
. For more information about the differences between versions, see the Durable Functions versions article.
The previous example listens for any of multiple events. It's also possible to wait for all events.
[FunctionName("NewBuildingPermit")]
public static async Task Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
string applicationId = context.GetInput<string>();
var gate1 = context.WaitForExternalEvent("CityPlanningApproval");
var gate2 = context.WaitForExternalEvent("FireDeptApproval");
var gate3 = context.WaitForExternalEvent("BuildingDeptApproval");
// all three departments must grant approval before a permit can be issued
await Task.WhenAll(gate1, gate2, gate3);
await context.CallActivityAsync("IssueBuildingPermit", applicationId);
}
Note
The previous code is for Durable Functions 2.x. For Durable Functions 1.x, you must use DurableOrchestrationContext
instead of IDurableOrchestrationContext
. For more information about the differences between versions, see the Durable Functions versions article.
In .NET, if the event payload cannot be converted into the expected type T
, an exception is thrown.
The "wait-for-external-event" API waits indefinitely for some input. The function app can be safely unloaded while waiting. If and when an event arrives for this orchestration instance, it is awakened automatically and immediately processes the event.
Note
If your function app uses the Consumption Plan, no billing charges are incurred while an orchestrator function is awaiting an external event task, no matter how long it waits.
As with Activity Functions, external events have an at-least-once delivery guarantee. This means that, under certain conditions (like restarts, scaling, crashes, etc.), your application may receive duplicates of the same external event. Therefore, we recommend that external events contain some kind of ID that allows them to be manually de-duplicated in orchestrators.
Send events
You can use the "raise-event" API defined by the orchestration client binding to send an external event to an orchestration. You can also use the built-in raise event HTTP API to send an external event to an orchestration.
A raised event includes an instance ID, an eventName, and eventData as parameters. Orchestrator functions handle these events using the "wait-for-external-event" APIs. The eventName must match on both the sending and receiving ends in order for the event to be processed. The event data must also be JSON-serializable.
Internally, the "raise-event" mechanisms enqueue a message that gets picked up by the waiting orchestrator function. If the instance is not waiting on the specified event name, the event message is added to an in-memory queue. If the orchestration instance later begins listening for that event name, it will check the queue for event messages.
Note
If there is no orchestration instance with the specified instance ID, the event message is discarded.
Below is an example queue-triggered function that sends an "Approval" event to an orchestrator function instance. The orchestration instance ID comes from the body of the queue message.
[FunctionName("ApprovalQueueProcessor")]
public static async Task Run(
[QueueTrigger("approval-queue")] string instanceId,
[DurableClient] IDurableOrchestrationClient client)
{
await client.RaiseEventAsync(instanceId, "Approval", true);
}
Note
The previous C# code is for Durable Functions 2.x. For Durable Functions 1.x, you must use OrchestrationClient
attribute instead of the DurableClient
attribute, and you must use the DurableOrchestrationClient
parameter type instead of IDurableOrchestrationClient
. For more information about the differences between versions, see the Durable Functions versions article.
Internally, the "raise-event" API enqueues a message that gets picked up by the waiting orchestrator function. If the instance is not waiting on the specified event name, the event message is added to an in-memory buffer. If the orchestration instance later begins listening for that event name, it will check the buffer for event messages and trigger the task that was waiting for it.
Note
If there is no orchestration instance with the specified instance ID, the event message is discarded.
HTTP
The following is an example of an HTTP request that raises an "Approval" event to an orchestration instance.
POST /runtime/webhooks/durabletask/instances/MyInstanceId/raiseEvent/Approval&code=XXX
Content-Type: application/json
"true"
In this case, the instance ID is hardcoded as MyInstanceId.