Edit

Quickstart: Use work item filtering with the Durable Task SDKs

In this quickstart, you learn how to run a Durable Task SDK sample that uses work item filtering to route orchestrations and activities to dedicated workers.

Work item filtering is currently available in the .NET, Java, and Python Durable Task SDKs. Select your language by using the tabs at the top of the page. To learn how work item filtering works and how it enables flat networking across heterogeneous compute, see Work item filtering in the Durable Task Scheduler.

Note

Work item filtering isn't available yet for the JavaScript and PowerShell Durable Task SDKs. To try this feature today, select the C#, Java, or Python tab at the top of the page.

  • Set up and run the Durable Task Scheduler emulator for local development.
  • Run the Orchestrator, Validator, and Shipper workers and the client.
  • Verify that work items are routed only to matching workers.
  • Deploy the sample to Azure Container Apps using Azure Developer CLI.

Prerequisites

Before you begin:

Prepare the project

From the Azure-Samples/Durable-Task-Scheduler root directory, navigate to the sample directory:

cd samples/scenarios/WorkItemFilteringSplitActivities

Run locally with the emulator

  1. Pull the emulator image:

    docker pull mcr.microsoft.com/durable-task/emulator:latest
    
  2. Run the emulator:

    docker run -d --name dts-emulator -p 8080:8080 -p 8082:8082 mcr.microsoft.com/durable-task/emulator:latest
    
  3. Build the sample:

    dotnet build
    
  4. Run each worker in a separate terminal:

    Terminal 1 - Orchestrator worker

    dotnet run --project src/OrchestratorWorker
    

    Terminal 2 - Validator worker

    dotnet run --project src/ValidatorWorker
    

    Terminal 3 - Shipper worker

    dotnet run --project src/ShipperWorker
    
  5. In a fourth terminal, run the client:

    dotnet run --project src/Client
    
  6. Open the emulator dashboard at http://localhost:8082 to monitor orchestration activity.

Tip

As an alternative to opening four terminals manually, you can run the included convenience script:

./run-local.sh

It starts the emulator, builds the solution, and launches all workers and the client.

Expected output from work item filtering

The client schedules a batch of three orchestrations every 30 seconds and keeps looping for about 10 minutes. Across the terminals, you see:

  • The client schedules orchestration batches and waits for each batch to complete. A finished batch logs 3 completed, 0 failed.
  • The Orchestrator worker dispatches ValidateOrder and ShipOrder activity calls.
  • The Validator worker handles only ValidateOrder activity work items.
  • The Shipper worker handles only ShipOrder activity work items.

This behavior confirms that work item filtering routes items only to workers that registered matching task types.

When you see at least one batch report 3 completed, 0 failed, the sample is working as expected. You don't have to wait for the full loop to finish - press Ctrl+C in each terminal to stop the client and workers. If you let the client run to the end, it prints a final results summary and then stays alive until you stop it with Ctrl+C.

Try it: Strict routing experiment

To see that routing is strict (no fallback to other workers):

  1. Stop the Shipper worker (Ctrl+C in Terminal 3).
  2. Run the client again to schedule new orchestrations.
  3. Observe that:
    • The Orchestrator worker picks up and starts orchestrations.
    • The Validator worker completes ValidateOrder for each order.
    • ShipOrder work items remain pending - the Validator and Orchestrator workers don't receive them.
    • The orchestrations stay in "Running" status, waiting for ShipOrder to complete.
  4. Restart the Shipper worker - the pending ShipOrder work items are delivered immediately and the orchestrations complete.

This experiment demonstrates that work items are routed only to workers with matching filters. There's no fallback.

Deploy using Azure Developer CLI

  1. Authenticate with Azure (if you haven't already):

    azd auth login
    
  2. From samples/scenarios/WorkItemFilteringSplitActivities, run:

    azd up
    
  3. When prompted in the terminal, provide the following parameters:

    Parameter Description
    Environment Name Prefix for the resource group created to hold all Azure resources.
    Azure Location The Azure location for your resources.
    Azure Subscription The Azure subscription for your resources.

The azd up command provisions Azure resources and deploys four containerized services from this sample: client, orchestrator worker, validator worker, and shipper worker.

Confirm successful deployment

  1. In the azd up output, copy the resource group name.

  2. In the Azure portal, open that resource group.

  3. For each deployed container app (client, orchestrator-worker, validator-worker, shipper-worker), open Monitoring > Log stream.

  4. Verify each app logs only its expected work items:

    • orchestrator-worker: orchestration work.
    • validator-worker: ValidateOrder activity.
    • shipper-worker: ShipOrder activity.

Note

Each Container App is configured with a KEDA scale rule (azure-durabletask-scheduler) that automatically scales workers from 0 to 10 replicas based on the pending work item backlog. When the client finishes its loop and no work items remain, workers scale back to zero. For more information, see Durable Task Scheduler autoscale on Azure Container Apps.

Understand the work item filtering code in C#

The orchestration calls two activities in sequence. The scheduler routes each activity work item to the worker that registered it.

public override async Task<string> RunAsync(TaskOrchestrationContext context, string orderId)
{
    string validationResult = await context.CallActivityAsync<string>("ValidateOrder", orderId);
    string shippingResult = await context.CallActivityAsync<string>("ShipOrder", orderId);

    return $"Order '{orderId}' => Validation: [{validationResult}], Shipping: [{shippingResult}]";
}

Each worker registers only its local tasks. The SDK automatically generates work item filters from the task registry, so you don't need to explicitly opt in.

builder.Services.AddDurableTaskWorker()
    .AddTasks(registry =>
    {
        // Only this worker's tasks are registered.
        // Work item filters are auto-generated from the registry, so this
        // worker only receives matching work items.
        registry.AddAllGeneratedTasks();
    })
    .UseDurableTaskScheduler(connectionString);

Note

In the .NET Durable Task SDK, work item filters are generated automatically from whatever each worker registers. To override the generated filters or opt out of filtering, call UseWorkItemFilters(customFilters) or UseWorkItemFilters(null), respectively.

For example:

  • OrchestratorWorker registers OrderProcessingOrchestration.
  • ValidatorWorker registers ValidateOrder.
  • ShipperWorker registers ShipOrder.

When a worker connects to Durable Task Scheduler, the SDK sends its filter list. The scheduler creates per-filter queues and routes each work item to the matching queue. Workers never receive work item types they didn't register.

Clean up resources

  1. Stop the local emulator container.

    docker rm -f dts-emulator
    
  2. If you deployed to Azure, identify the resource group name:

    • Copy it from the azd up output.
    • Or in the Azure portal, use the global search box at the top and search for rg- or your environment name prefix.
  3. Open the resource group from the search results.

  4. Select Delete resource group, enter the resource group name to confirm, and then select Delete.

Next steps

  • Set up and run the Durable Task Scheduler emulator for local development.
  • Run the Orchestrator, Validator, and Shipper workers and the client.
  • Verify that work items are routed only to matching workers.
  • Deploy the sample to Azure Container Apps using Azure Developer CLI.

Prerequisites

Before you begin:

Prepare the project

From the Azure-Samples/Durable-Task-Scheduler root directory, go to the sample directory:

cd samples/scenarios/WorkItemFilteringSplitActivitiesJava

Run locally with the emulator

  1. Pull the emulator image.

    docker pull mcr.microsoft.com/dts/dts-emulator:latest
    
  2. Run the emulator.

    docker run -d --name dts-emulator -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
    
  3. Build the sample.

    ./gradlew build
    
  4. Run each worker in a separate terminal:

    Terminal 1 - Orchestrator worker

    ./gradlew :orchestrator-worker:run
    

    Terminal 2 - Validator worker

    ./gradlew :validator-worker:run
    

    Terminal 3 - Shipper worker

    ./gradlew :shipper-worker:run
    
  5. In a fourth terminal, run the client.

    ./gradlew :client:run
    
  6. Open the emulator dashboard at http://localhost:8082 to monitor orchestration activity.

Expected output from work item filtering

The client schedules a batch of three orchestrations every 30 seconds and keeps looping for about 10 minutes. Across the terminals, you see:

  • The client schedules orchestration batches and waits for each batch to complete. A finished batch logs 3 completed, 0 failed.
  • The Orchestrator worker dispatches ValidateOrder and ShipOrder activity calls.
  • The Validator worker handles only ValidateOrder activity work items.
  • The Shipper worker handles only ShipOrder activity work items.

This behavior confirms that work item filtering routes items only to workers that registered matching task types.

When you see at least one batch report 3 completed, 0 failed, the sample is working as expected. You don't have to wait for the full loop to finish - press Ctrl+C in each terminal to stop the client and workers. If you let the client run to the end, it prints a final results summary and then stays alive until you stop it with Ctrl+C.

Try it: Strict routing experiment

To see that routing is strict (no fallback to other workers):

  1. Stop the Shipper worker (Ctrl+C in Terminal 3).
  2. Run the client again to schedule new orchestrations.
  3. Observe that:
    • The Orchestrator worker picks up and starts orchestrations.
    • The Validator worker completes ValidateOrder for each order.
    • ShipOrder work items remain pending - the Validator and Orchestrator workers don't receive them.
    • The orchestrations stay in "Running" status, waiting for ShipOrder to complete.
  4. Restart the Shipper worker - the pending ShipOrder work items are delivered immediately and the orchestrations complete.

This experiment demonstrates that work items are routed only to workers with matching filters. There's no fallback.

Deploy using Azure Developer CLI

  1. Authenticate with Azure (if you haven't already):

    azd auth login
    
  2. From samples/scenarios/WorkItemFilteringSplitActivitiesJava, run:

    azd up
    
  3. When prompted in the terminal, provide the following parameters:

    Parameter Description
    Environment Name Prefix for the resource group created to hold all Azure resources.
    Azure Location The Azure location for your resources.
    Azure Subscription The Azure subscription for your resources.

The azd up command provisions Azure resources and deploys four containerized services from this sample: client, orchestrator worker, validator worker, and shipper worker.

Confirm successful deployment

  1. In the azd up output, copy the resource group name.

  2. In the Azure portal, open that resource group.

  3. For each deployed container app (client, orchestrator-worker, validator-worker, shipper-worker), open Monitoring > Log stream.

  4. Verify each app logs only its expected work items:

    • orchestrator-worker: orchestration work.
    • validator-worker: ValidateOrder activity.
    • shipper-worker: ShipOrder activity.

Note

Each Container App is configured with a KEDA scale rule (azure-durabletask-scheduler) that automatically scales workers from 0 to 10 replicas based on the pending work item backlog. When the client finishes its loop and no work items remain, workers scale back to zero. For more information, see Durable Task Scheduler autoscale on Azure Container Apps.

Understand the work item filtering code in Java

The orchestration calls two activities in sequence. The scheduler routes each activity work item to the worker that registered it.

String validationResult = ctx.callActivity("ValidateOrder", orderId, String.class).await();
String shippingResult = ctx.callActivity("ShipOrder", orderId, String.class).await();

String combined = String.format("Order '%s' => Validation: [%s], Shipping: [%s]",
        orderId, validationResult, shippingResult);
ctx.complete(combined);

Each worker registers only its local tasks and calls useWorkItemFilters() to opt in to filtering. The SDK then generates work item filters from the task registry.

DurableTaskGrpcWorker worker = DurableTaskSchedulerWorkerExtensions
        .createWorkerBuilder(connectionString)
        .addActivity(new TaskActivityFactory() {
            // ...
        })
        .useWorkItemFilters() // auto-generate from registered tasks
        .build();

Note

Each worker must explicitly call useWorkItemFilters() to enable filtering. Workers that don't call it receive all work item types.

For example:

  • OrchestratorWorker registers OrderProcessingOrchestration.
  • ValidatorWorker registers ValidateOrder.
  • ShipperWorker registers ShipOrder.

When a worker connects to Durable Task Scheduler, the SDK sends its filter list. The scheduler creates per-filter queues and routes each work item to the matching queue. Workers never receive work item types they didn't register.

To supply explicit filters instead of auto-generating them from the registry, pass a WorkItemFilter to useWorkItemFilters():

WorkItemFilter filter = WorkItemFilter.newBuilder()
        .addOrchestration("OrderProcessingOrchestration")
        .addActivity("ValidateOrder")
        .build();

builder.useWorkItemFilters(filter);

Clean up resources

  1. Stop the local emulator container.

    docker rm -f dts-emulator
    
  2. If you deployed to Azure, identify the resource group name:

    • Copy it from the azd up output.
    • Or in the Azure portal, use the global search box at the top and search for rg- or your environment name prefix.
  3. Open the resource group from the search results.

  4. Select Delete resource group, enter the resource group name to confirm, and then select Delete.

Next steps

  • Set up and run the Durable Task Scheduler emulator for local development.
  • Run Worker A, Worker B, and the client.
  • Verify that work items are routed only to matching workers.

Prerequisites

Before you begin:

Prepare the project

From the Azure-Samples/Durable-Task-Scheduler root directory, go to the sample directory:

cd samples/durable-task-sdks/python/work-item-filtering

Run locally with the emulator

  1. Pull the emulator image.

    docker pull mcr.microsoft.com/dts/dts-emulator:latest
    
  2. Run the emulator.

    docker run -d --name dts-emulator -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
    

    The sample uses the default emulator settings (endpoint http://localhost:8080 and task hub default), so you don't need to set any environment variables.

  3. (Optional) Create and activate a Python virtual environment.

    python -m venv venv
    source venv/bin/activate  # On Windows, use: venv\Scripts\activate
    
  4. Install the required packages.

    pip install -r requirements.txt
    
  5. Run each worker in a separate terminal:

    Terminal 1 - Worker A (greeting)

    python worker_a.py
    

    Terminal 2 - Worker B (math)

    python worker_b.py
    
  6. In a third terminal, run the client.

    python client.py
    
  7. Open the emulator dashboard at http://localhost:8082 to monitor orchestration activity.

Expected output from work item filtering

The client schedules a greeting orchestration and a math orchestration, and then waits for both to complete. Across the terminals, you see:

  • Worker A processes only the greeting orchestration and its say_hello activity.
  • Worker B processes only the math orchestration and its add_numbers activity.
  • The client prints the result from each orchestration.

This behavior confirms that work item filtering routes items only to workers that registered matching task types.

Understand the work item filtering code in Python

Each worker registers only its local tasks and calls use_work_item_filters() to opt in to filtering. The SDK generates the work item filters from the registered tasks.

worker.add_orchestrator(greeting_orchestrator)
worker.add_activity(say_hello)
worker.use_work_item_filters()  # Auto-generate from the registry

Note

Each worker must explicitly call use_work_item_filters() to enable filtering. Workers that don't call it receive all work item types.

For example:

  • Worker A registers greeting_orchestrator and say_hello.
  • Worker B registers math_orchestrator and add_numbers.

When a worker connects to Durable Task Scheduler, the SDK sends its filter list. The scheduler creates per-filter queues and routes each work item to the matching queue. Workers never receive work item types they didn't register.

To supply explicit filters instead of auto-generating them from the registry, pass WorkItemFilters:

from durabletask.worker import WorkItemFilters, OrchestrationWorkItemFilter, ActivityWorkItemFilter

worker.use_work_item_filters(WorkItemFilters(
    orchestrations=[
        OrchestrationWorkItemFilter(name="greeting_orchestrator"),
    ],
    activities=[
        ActivityWorkItemFilter(name="say_hello"),
    ],
))

Clean up resources

Stop the local emulator container.

docker rm -f dts-emulator

Next steps