.NET Continue-As-New implementation using CreateContinueAsNewException
In the .NET SDK, throw a CreateContinueAsNewException exception inside your Workflow to implement Continue-As-New. This stops the Workflow right away and starts a new one with the provided parameters.
Check Workflow.AllHandlersFinished before Continue-As-New
When implementing Continue-As-New in a Workflow with message handlers, verify that Workflow.AllHandlersFinished returns true before proceeding with Continue-As-New to ensure all Update or Signal handlers have completed.
Continue-As-New example with state passing in .NET
throw Workflow.CreateContinueAsNewException((ClusterManagerWorkflow wf) => wf.RunAsync(new()
{
State = CurrentState,
TestContinueAsNew = input.TestContinueAsNew,
}));
Check Continue-As-New suggestion with Workflow.ContinueAsNewSuggested
In .NET, call Workflow.ContinueAsNewSuggested to check if Temporal suggests it is time to Continue-As-New based on Event History tracking.
.NET SDK Workflow documentation structure
The .NET SDK Workflow documentation covers the following topics: Workflow basics, Child Workflows, Continue-As-New, Cancellation, Timeouts, Message passing, Schedules, Timers, Dynamic Workflow, and Versioning.
Dynamic Workflow parameter type in .NET SDK
The Workflow Definition for a Dynamic Workflow must accept a single argument of type Temporalio.Converters.IRawValue[]. The Workflow.PayloadConverter property is used to convert an IRawValue object to the desired type using extension methods in the Temporalio.Converters namespace.
Dynamic Workflow example in .NET SDK
Example of a Dynamic Workflow in .NET SDK:
```csharp
[Workflow(Dynamic = true)]
public class DynamicWorkflow
{
[WorkflowRun]
public async Task<string> RunAsync(IRawValue[] args)
{
var name = Workflow.PayloadConverter.ToValue<string>(args.Single());
var param = MyActivityParams("Hello", name);
return await Workflow.ExecuteActivityAsync(
(MyActivities a) => a.MyActivity(param),
new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) });
}
}
```
This example shows how to define a Dynamic Workflow that accepts raw arguments, converts them to the desired type, and executes an activity with those parameters.
Set Dynamic Workflow in .NET SDK
A Dynamic Workflow in Temporal is a Workflow that is invoked dynamically at runtime if no other Workflow with the same name is registered. A Workflow can be made dynamic by setting Dynamic as true on the [Workflow] attribute. Only one Dynamic Workflow can be present on a Worker. You must register the Workflow with the Worker before it can be invoked.
Pause scheduled workflow example in .NET
using Temporalio.Client;
using Temporalio.Client.Schedules;
var client = await TemporalClient.ConnectAsync(new("localhost:7233"));
var handle = client.GetScheduleHandle("my-schedule-id");
await handle.PauseAsync("Pausing the schedule for now");
DeleteAsync method for deleting schedules
To delete a Scheduled Workflow Execution in .NET, use the DeleteAsync() method on the Schedule Handle. When you delete a Schedule, it does not affect any Workflows that were already started by the Schedule.
Update scheduled workflow example in .NET
using Temporalio.Client;
using Temporalio.Client.Schedules;
var client = await TemporalClient.ConnectAsync(new("localhost:7233"));
var handle = client.GetScheduleHandle("my-schedule-id");
await handle.UpdateAsync(input =>
{
var newAction = ScheduleActionStartWorkflow.Create(
(MyWorkflow wf) => wf.RunAsync(),
new(id: "my-workflow-id", taskQueue: "my-task-queue"));
return new(input.Description.Schedule with { Action = newAction });
});
UpdateAsync method for updating schedules
To update a Scheduled Workflow Execution in .NET, use the UpdateAsync() method on the Schedule Handle. This method accepts a callback that provides input with the current schedule. A new schedule can be created and returned from that callback to perform the update. This is useful when you need to modify the Schedule's configuration, such as changing the start time, end time, or interval.
PauseAsync method for pausing schedules
To pause a Scheduled Workflow Execution in .NET, use the PauseAsync() method on the Schedule Handle. When you pause a Schedule, all the future Workflow Runs associated with the Schedule are temporarily stopped. You can pass a note to the PauseAsync() method to provide a reason for pausing the schedule.
DescribeAsync method for viewing schedule configuration and runs
To describe a Scheduled Workflow Execution in .NET, use the DescribeAsync() method on the Schedule Handle. This shows the current Schedule configuration, including information about past, current, and future Workflow Runs.
List schedules example in .NET
using Temporalio.Client;
using Temporalio.Client.Schedules;
var client = await TemporalClient.ConnectAsync(new("localhost:7233"));
await foreach (var desc in client.ListSchedulesAsync())
{
Console.WriteLine("Schedule info: {0}", desc.Info);
}
StartDelay usage example in .NET
var handle = await client.StartWorkflowAsync(
(MyWorkflow wf) => wf.RunAsync(),
new(id: "my-workflow-id", taskQueue: "my-task-queue")
{
StartDelay = TimeSpan.FromHours(3),
});
Create scheduled workflow example in .NET
using Temporalio.Client;
using Temporalio.Client.Schedules;
var client = await TemporalClient.ConnectAsync(new("localhost:7233"));
var handle = await client.CreateScheduleAsync(
"my-schedule-id",
new(
Action: ScheduleActionStartWorkflow.Create(
(MyWorkflow wf) => wf.RunAsync(),
new(id: "my-workflow-id", taskQueue: "my-task-queue")),
Spec: new()
{
Intervals = new List<ScheduleIntervalSpec> { new(Every: TimeSpan.FromDays(5)) },
}));
Trigger scheduled workflow example in .NET
using Temporalio.Client;
using Temporalio.Client.Schedules;
var client = await TemporalClient.ConnectAsync(new("localhost:7233"));
var handle = client.GetScheduleHandle("my-schedule-id");
await handle.TriggerAsync();
TriggerAsync method for triggering immediate schedule execution
To trigger a Scheduled Workflow Execution in .NET, use the TriggerAsync() method on the Schedule Handle. This triggers an immediate action with a given Schedule. By default, this action is subject to the Overlap Policy of the Schedule.
Backfill scheduled workflow example in .NET
using Temporalio.Client;
using Temporalio.Client.Schedules;
var client = await TemporalClient.ConnectAsync(new("localhost:7233"));
var handle = client.GetScheduleHandle("my-schedule-id");
var now = DateTime.Now;
await handle.BackfillAsync(new List<ScheduleBackfill>
{
new(
StartAt: now - TimeSpan.FromDays(30),
EndAt: now - TimeSpan.FromDays(20),
Overlap: ScheduleOverlapPolicy.AllowAll),
});
Describe scheduled workflow example in .NET
using Temporalio.Client;
using Temporalio.Client.Schedules;
var client = await TemporalClient.ConnectAsync(new("localhost:7233"));
var handle = client.GetScheduleHandle("my-schedule-id");
var desc = await handle.DescribeAsync();
Console.WriteLine("Schedule info: {0}", desc.Info);
CreateScheduleAsync method for creating scheduled workflows
To create a Scheduled Workflow Execution in .NET, use the CreateScheduleAsync method on the Client. Pass the Schedule ID and the Schedule object to the method. Set the Schedule's Action property to an instance of ScheduleActionStartWorkflow to schedule a Workflow Execution.
ListSchedulesAsync method for listing all schedules
To list all schedules in .NET, use the ListSchedulesAsync() asynchronous method on the Client. This returns an async enumerable. If a schedule is added or deleted, it may not be available in the list immediately.
BackfillAsync method for executing scheduled actions ahead of time
To backfill a Scheduled Workflow Execution in .NET, use the BackfillAsync() method on the Schedule Handle. Backfill executes Actions ahead of their specified time range, which is useful when you need to execute a missed or delayed Action, or when you want to test the Workflow before its scheduled time.
.NET RetryPolicy set in WorkflowOptions
The RetryPolicy can be set in the WorkflowOptions when calling StartWorkflowAsync or ExecuteWorkflowAsync.
.NET workflow timeout options in WorkflowOptions
Workflow timeouts are set in the WorkflowOptions when calling StartWorkflowAsync or ExecuteWorkflowAsync. The available timeout options are: ExecutionTimeout, RunTimeout, and TaskTimeout.
.NET set WorkflowExecutionTimeout example
var result = await client.ExecuteWorkflowAsync(
(MyWorkflow wf) => wf.RunAsync(),
new(id: "my-workflow-id", taskQueue: "my-task-queue")
{
WorkflowExecutionTimeout = TimeSpan.FromMinutes(5),
});
.NET set Workflow RetryPolicy example
var result = await client.ExecuteWorkflowAsync(
(MyWorkflow wf) => wf.RunAsync(),
new(id: "my-workflow-id", taskQueue: "my-task-queue")
{
RetryPolicy = new() { MaximumInterval = TimeSpan.FromSeconds(10) },
});
Durable Timer in .NET - Workflow.DelayAsync
Use Workflow.DelayAsync to pause the execution of a Workflow for a specified duration in the .NET SDK. Timers are persisted, so even if your Worker or Temporal Service is down when the time period completes, as soon as both are back up, the Durable Timer call will resolve and code execution will continue.
Workflow.DelayAsync example - .NET
// Sleep for 3 days
await Workflow.DelayAsync(TimeSpan.FromDays(3));
Workflow.DelayAsync deterministic behavior
Workflow.DelayAsync is a deterministic form of Task.Delay, suitable for use within Workflows.
Workflow message handlers: Queries, Signals, Updates
A Workflow acts as a stateful service that receives messages through three handler types. Query handlers retrieve state synchronously. Signal handlers send asynchronous messages to change Workflow state. Update handlers send trackable synchronous requests that can change state and return results. All three handler types are defined as methods on the Workflow class using their respective attributes: WorkflowQueryAttribute, WorkflowSignalAttribute, and WorkflowUpdateAttribute.
Send Update with StartUpdateAsync to receive handle
Use WorkflowHandle.StartUpdateAsync to receive an UpdateHandle as soon as the Update is accepted. This returns immediately after the Worker accepts or rejects the Update, not after asynchronous operations complete. Use the UpdateHandle later with GetResultAsync to fetch results: var updateHandle = await workflowHandle.StartUpdateAsync(wf => wf.SetGreetingAsync(new HelloWorldInput("World")), new(waitForStage: WorkflowUpdateStage.Accepted)); var updateResult = await updateHandle.GetResultAsync();
Update errors: Workflow Task failure
A Workflow Task Failure causes the server to retry Workflow Tasks indefinitely. If the Update request hasn't been accepted, you receive a FAILED_PRECONDITION RpcException. If accepted, it is durable and once the Workflow recovers after a code deploy, use an UpdateHandle to fetch the Update result.
Non-type-safe APIs for message sending
When you don't have access to Workflow Definition signatures, use non-type-safe APIs. Pass method names as strings instead of lambda expressions to TemporalClient.StartWorkflowAsync, WorkflowHandle.QueryAsync, WorkflowHandle.SignalAsync, WorkflowHandle.ExecuteUpdateAsync, and WorkflowHandle.StartUpdateAsync. Use non-type-safe overloads of TemporalClient.GetWorkflowHandle and Workflow.GetExternalWorkflowHandle.
Signal handler definition
A Signal handler is defined using the [WorkflowSignal] attribute and changes Workflow state asynchronously. The handler should not return a value; the response is sent immediately from the server without waiting for the Workflow to process the Signal. Signal handlers can be asynchronous and blocking, allowing use of Activities, Child Workflows, durable Workflow.DelayAsync Timers, Workflow.WaitConditionAsync conditions, and similar operations. The Signal attribute can accept arguments.
Workflows.Semaphore for handler concurrency
Use Workflows.Semaphore as an alternative to Mutex for managing access to shared resources and coordinating the order in which handlers execute.
Query errors: Query failed
If something goes wrong during a Query, you receive Temporalio.Exceptions.WorkflowQueryFailedException. Any exception in a Query handler triggers this error. This differs from Signal and Update requests, where exceptions can lead to Workflow Task Failure.
Send Update from client with ExecuteUpdateAsync
To send an Update to a Workflow Execution and wait for it to complete, use WorkflowHandle.ExecuteUpdateAsync: var previousLanguage = await workflowHandle.ExecuteUpdateAsync(wf => wf.SetCurrentLanguageAsync(GreetingWorkflow.Language.Chinese));
Asynchronous handlers with Activities and Timers
Signal and Update handlers can be asynchronous and blocking, allowing you to await Activities, Child Workflows, durable Workflow.DelayAsync Timers, Workflow.WaitConditionAsync conditions, and other async operations. Using asynchronous calls expands possibilities but means handler executions and the main Workflow method run concurrently with switching at await points. Understand concurrent execution patterns to use async handlers safely.
Update handler definition with validators
An Update handler is defined using the [WorkflowUpdate] attribute and is a trackable synchronous request that can change Workflow state, control flow, and return a result. The sender waits until the Worker accepts or rejects the Update and may wait further to receive a result or exception. Update handlers can be asynchronous and blocking. An Update validator is defined using the [WorkflowUpdateValidator] attribute and uses the Name argument to connect it to its Update handler. The validator must be void and accept the same argument types as the handler. Validators are optional and used to reject Updates before they are written to History by raising an exception. Without a validator, Updates are always accepted. The WorkflowExecutionUpdateAccepted event is written to History whether acceptance is automatic or programmatic; when a validator raises an error, the Update is rejected and WorkflowExecutionUpdateAccepted is not added to the Event History.
RpcException errors when sending messages
When sending a Signal, Update, or Query to a Workflow, the Client might receive Temporalio.Exceptions.RpcException. If the Client can't contact the server, RpcException.Code has a status of Unavailable (after retries). If the Workflow does not exist, RpcException.Code has a status of NotFound.
Dynamic Query handler
A Dynamic Query is invoked dynamically at runtime if no static Query with the same name is registered. Set Dynamic to true on the [WorkflowQuery] attribute. Only one Dynamic Query can exist on a Workflow. The Query handler parameters must accept a string name and Temporalio.Converters.IRawValue[] for arguments. Use Workflow.PayloadConverter to convert IRawValue objects to desired types.
Query handler definition and constraints
A Query handler is defined using the [WorkflowQuery] attribute and can be implemented as a method or property getter. Query handlers must not modify Workflow state and cannot perform async blocking operations such as executing Activities. The Query attribute can accept arguments. A Worker must be online and polling the Task Queue to process a Query. Queries can be sent to closed Workflow Executions within a Namespace's Workflow retention period, including completed, failed, or timed out Workflows, but not terminated ones. Sending a Query does not add events to a Workflow's Event History.
Dynamic Query example
[WorkflowQuery(Dynamic = true)] public string DynamicQueryAsync(string queryName, IRawValue[] args) { var input = Workflow.PayloadConverter.ToValue<MyStatusParam>(args.Single()); return statuses[input.Type]; }
Query errors: no Worker polling
When there is no Workflow Worker polling the Task Queue, you receive an RpcException with Code having a status of FailedPrecondition.
Async handler example with Activity and Mutex
An async Update handler can execute Activities and use a Mutex to ensure serialization: [WorkflowUpdate] public async Task<Language> SetLanguageAsync(Language language) { await mutex.WaitOneAsync(); try { if (!greetings.ContainsKey(language)) { var greeting = Workflow.ExecuteActivityAsync((MyActivities acts) => acts.CallGreetingServiceAsync(language), new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) }); if (greeting == null) { throw new ApplicationFailureException($"Greeting service does not support {language}"); } greetings[language] = greeting; } var previousLanguage = CurrentLanguage; CurrentLanguage = language; return previousLanguage; } finally { mutex.ReleaseMutex(); } }
Dynamic Signal handler
A Dynamic Signal is invoked dynamically at runtime if no static Signal with the same name is registered. Set Dynamic to true on the [WorkflowSignal] attribute. Only one Dynamic Signal can exist on a Workflow. The Signal handler parameters must accept a string name and Temporalio.Converters.IRawValue[] for arguments. Use Workflow.PayloadConverter to convert IRawValue objects to desired types.
Signal errors and exceptions
When using Signal, the only exception that results from requests during execution is RpcException. All handlers may experience additional exceptions during the initial pre-Worker part of the handler request lifecycle.
WorkflowInit example
[Workflow] public class WorkflowInitWorkflow { public record Input(string Name); private readonly string nameWithTitle; private bool titleHasBeenChecked; [WorkflowInit] public WorkflowInitWorkflow(Input input) => nameWithTitle = $"Sir {input.Name}"; [WorkflowRun] public async Task<string> RunAsync(Input ignored) { await Workflow.WaitConditionAsync(() => titleHasBeenChecked); return $"Hello, {nameWithTitle}"; } [WorkflowUpdate] public async Task<bool> CheckTitleValidityAsync() { var valid = await Workflow.ExecuteActivityAsync((MyActivities acts) => acts.CheckTitleValidityAsync(nameWithTitle), new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) }); titleHasBeenChecked = true; return valid; } }
Dynamic Signal example
[WorkflowSignal(Dynamic = true)] public async Task DynamicSignalAsync(string signalName, IRawValue[] args) { var input = Workflow.PayloadConverter.ToValue<DoSomethingParam>(args.Single()); pendingThings.Add(input); }
Dynamic Update handler
A Dynamic Update is invoked dynamically at runtime if no static Update with the same name is registered. Set Dynamic to true on the [WorkflowUpdate] attribute. Only one Dynamic Update can exist on a Workflow. The Update handler parameters must accept a string name and Temporalio.Converters.IRawValue[] for arguments. Use Workflow.PayloadConverter to convert IRawValue objects to desired types.
Wait conditions in handlers with Workflow.WaitConditionAsync
Use Workflow.WaitConditionAsync to set a function that prevents handler code from proceeding until the condition returns true. This is useful for waiting until appropriate conditions exist before continuing. Example: [WorkflowUpdate] public async Task<string> MyUpdateAsync(UpdateInput updateInput) { await Workflow.WaitConditionAsync(() => ReadyForUpdateToExecute(updateInput)); // ... }
Send Query from client using WorkflowHandle.QueryAsync
To send a Query to a Workflow Execution, use WorkflowHandle.QueryAsync with a lambda expression invoking the Query handler: var supportedLanguages = await workflowHandle.QueryAsync(wf => wf.GetLanguages(new(false)));
Send Signal from client using WorkflowHandle.SignalAsync
To send a Signal from a Client to a Workflow Execution, use WorkflowHandle.SignalAsync with a lambda expression: await workflowHandle.SignalAsync(wf => wf.ApproveAsync(new("MyUser"))); The call returns when the server accepts the Signal; it does not wait for the Signal to be delivered to the Workflow Execution. The WorkflowExecutionSignaled Event appears in the Workflow's Event History.
Use Workflows.Mutex to prevent concurrent handler execution
Use Workflows.Mutex, a mutual exclusion lock, to coordinate access when multiple handler instances may execute concurrently. Locking ensures only one handler instance can execute a specific section at any given time. Example: private readonly Mutex mutex = new(); [WorkflowSignal] public async Task SafeHandlerAsync() { await mutex.WaitOneAsync(); try { // handler code } finally { mutex.ReleaseMutex(); } }
Update errors: failed or rejected
When an Update fails, you receive Temporalio.Exceptions.WorkflowUpdateFailedException. This happens when: (1) The Update is rejected by an Update validator defined in the Workflow, or (2) The Update fails after acceptance. Update failures are like Workflow failures and can be caused by failed Child Workflows, failed Activities with finite retries, ApplicationFailure raised by the Workflow author, or errors listed in TemporalWorkerOptions.WorkflowFailureExceptionTypes or WorkflowAttribute.FailureExceptionTypes.
Dynamic Update example
[WorkflowUpdate(Dynamic = true)] public async Task<string> DynamicUpdateAsync(string updateName, IRawValue[] args) { var input = Workflow.PayloadConverter.ToValue<DoSomethingParam>(args.Single()); pendingThings.Add(input); return statuses[input.Type]; }
Struct methods not recommended for Workflows
While it is possible to register struct methods as Workflows, this practice is strongly discouraged. In some cases, struct methods as Workflows may cause non-deterministic errors. Struct methods should only be used for Activities.
workflow.Go() for creating goroutines in workflows
The Temporal Go SDK allows you to create additional goroutines (threads) in your Workflows by calling workflow.Go(). Native Go threading is never allowed in Workflow code, as it would create determinism errors.