Creating commands
In Taskurai, tasks execute commands that receive a payload containing arguments, initial state, and various settings. These commands are defined within workers and controllers, which can contain one or more commands.
Command names should be unique (case insensitive) within a Taskurai installation. Commands should be designed idempotent, meaning that they can be executed multiple times without changing the outcome.
Commands can easily be made durable and fault-tolerant and by using the Durable Steps & Workflows API.
Prerequisites
- You have completed:
Defining commands
Commands are defined in Controllers and marked with the [Command] attribute.
Both sync and async methods can be used as commands.
The return type of a command can be:
- Any serializable type.
TaskCommandResultwhich provides more extended information (status, progress, errors, ...) and supports multiple return values, state references and meta data.
Commands returning serializable types
Commands can retry any serializable type. To let the command fail, throw an exception.
- C#
using System.Net;
using Taskurai.Models;
using Taskurai.Worker;
public class TestControllerSerializable: WorkerController
{
private readonly IConfiguration _configuration;
public TestControllerSerializable(IConfiguration configuration)
{
_configuration = configuration;
}
[Command]
public async Task<string> GetIPLocation(
TaskuraiTaskContext context,
CancellationToken cancellationToken,
string ipv4)
{
Logger.LogInformation("Looking up IP location for {IpAddress}", ipv4);
if (string.IsNullOrEmpty(ipv4))
{
Logger.LogError("Invalid IP address provided: {IpAddress}", ipv4);
throw new ArgumentException("Invalid IP address");
}
// Simulate work
await Task.Delay(5000, cancellationToken);
var location = "Amsterdam, Netherlands";
Logger.LogInformation("IP lookup result for {IpAddress}: {Location}", ipv4, location);
// Simulate lookup
return location;
}
/// <summary>
/// Returns the current UTC time.
/// </summary>
/// <returns>Current UTC time <see cref="DateTimeOffset"/>.</returns>
[Command("UtcNow")]
[Version(1, IsDefault = true)]
public DateTimeOffset UtcNow()
{
return DateTimeOffset.UtcNow;
}
}
Commands returning TaskCommandResult
- C#
using System.Net;
using Taskurai.Models;
using Taskurai.Worker;
public class TestControllerTaskCommandResult: WorkerController
{
private readonly IConfiguration _configuration;
public TestControllerTaskCommandResult(IConfiguration configuration)
{
_configuration = configuration;
}
[Command]
public async Task<TaskCommandResult> GetIPLocationWithCommandResult(
TaskuraiTaskContext context,
CancellationToken cancellationToken,
string ipv4)
{
Logger.LogInformation("Looking up IP location for {IpAddress}", ipv4);
if (string.IsNullOrEmpty(ipv4))
{
Logger.LogError("Invalid IP address provided: {IpAddress}", ipv4);
return Failed(HttpStatusCode.BadRequest, "Invalid IP address");
}
// Simulate work
await Task.Delay(5000, cancellationToken);
var location = "Amsterdam, Netherlands";
Logger.LogInformation("IP lookup result for {IpAddress}: {Location}", ipv4, location);
return Succeeded(
result: new ResultResponse()
{
Messages = {
new ResultMessage()
{
Code = "200",
Message = $"Location info found for IP '{ipv4}'."
}
},
Outputs = {
new ResultOutput()
{
Name = "location",
Value = location
},
new ResultOutput()
{
Name = "coordinates",
Value = new { Latitude = 52.3730796, Longitude = 4.8924534 }
}
}
}
);
}
/// <summary>
/// Returns the current UTC time.
/// </summary>
/// <returns>Current UTC time <see cref="DateTimeOffset"/>.</returns>
[Command("UtcNowWithCommandResult")]
[Version(1, IsDefault = true)]
public TaskCommandResult UtcNowWithCommandResult()
{
return Succeeded(DateTimeOffset.UtcNow);
}
}
Arguments mapping
Commands can receive arguments from the task payload. Arguments can be of any serializable type.
Standard arguments:
- TaskuraiTaskContext: The task context.
- CancellationToken: The cancellation token.
Other arguments are mapped by name from the task payload and are can have the following types:
- Serializable type: Must match the type of the task argument.
- TaskArgument: Argument is returned as the
TaskArgument. - StateReference: The
StateReferenceis returned (if available on the argument).
To override the default argument mapping by name, use the [FromArgument] attribute.
Example:
- C#
public class Formatting
{
public string PaperSize { get; set; }
public string Orientation { get; set; }
}
public class Mailing
{
public bool IncludePurchaseOrderAttachment { get; set; }
public List<string> Recipients { get; set; }
}
/// <summary>
/// Test command.
/// </summary>
/// <param name="context">Taskurai Task context.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <param name="purchaseOrderId">Purchase order id.</param>
/// <param name="pdfFormatting">PDF formatting.</param>
/// <param name="mailing">Mailing.</param>
/// <param name="ccMailing">CC Mailing.</param>
/// <returns>TaskCommandResult</returns>
public async Task<TaskCommandResult> TestCommand(
TaskuraiTaskContext context,
CancellationToken cancellationToken,
string purchaseOrderId,
[FromArgument(Name = "formatting")] Formatting pdfFormatting,
Mailing mailing,
Mailing ccMailing)
{
// Simulate work
await Task.Delay(5000, cancellationToken);
return Succeeded();
}
Accessing task arguments
Task information can be accessed easily using the task context.
- C#
/// <summary>
/// Test context sample.
/// </summary>
/// <param name="context">Taskurai Task context.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>TaskCommandResult</returns>
public async Task<TaskCommandResult> TaskContextSample(TaskuraiTaskContext context, CancellationToken cancellationToken)
{
Logger.LogInformation("TaskContextSample started...");
// Log task information
var task = context.Task;
// Get input arguments
string purchaseOrderId = task.Arguments.GetValue<string>("purchaseOrderId");
Formatting formatting = task.Arguments.GetValue<Formatting>("formatting");
Mailing mailing = task.Arguments.GetValue<Mailing>("mailing");
// Simulate work
await Task.Delay(5000, cancellationToken);
return Succeeded();
}
Reporting progress, extending duration
Sending a heartbeat event
While running a task, it is recommended to send heartbeat events for long running tasks. Other processes can check the heartbeat information in the task record.
- C#
await context.ProgressAsync();
Progress, heartbeat
While running a task, it is possible to report on progress and send a heartbeat event and the same time.
- C#
await context.ProgressAsync(
percentage: 10,
message: $"Requesting ocr for invoice {upload.Id}...");
Progress, heartbeat and extending duration
Commands that are processing longer than the default worker command timeout, should extend the command duration using the progress method, in order to prevent the task to be failing and marked as timed-out.
The command timeout can be extended multiple times, preferably in small intervals, to ensure continued processing. The command timeout is set in seconds (now + command timeout).
- C#
await context.ProgressAsync(
percentage: 10,
message: $"Requesting ocr for invoice...",
commandTimeout: 3600); // 1 hour left
Expiration status
To prevent tasks from starting after a due time, it is possible to specify the notStartAfter in the task execution options.
Tasks that are not actively running will not be started by Taskurai, to check if running tasks have expired, use the CheckTaskExpired on the task context.
- C#
// Check if task may start after acquiring lock
context.CheckTaskExpired();
Completing a task
Each task must be completed in Taskurai with a final state. The final state can be a success, failure, or canceled state.
Completing with serializable return types
Commands are completed by either returning a serializable type (Succeeded) or by throwing an exception (Failed).
Example:
- C#
[Command]
public async Task<string> GetIPLocation(
TaskuraiTaskContext context,
CancellationToken cancellationToken,
string ipv4)
{
if (string.IsNullOrEmpty(ipv4))
{
// Return a failed state
throw new ArgumentException("Invalid IP address");
}
// Simulate work
await Task.Delay(5000, cancellationToken);
// Returning a succeeded state
return "Amsterdam, Netherlands";
}
Completing with TaskCommandResult
- C#
[Command]
public async Task<TaskCommandResult> GetIPLocationWithCommandResult(
TaskuraiTaskContext context,
CancellationToken cancellationToken,
string ipv4)
{
Logger.LogInformation("Looking up IP location for {IpAddress}", ipv4);
if (string.IsNullOrEmpty(ipv4))
{
// Return a failed state
Logger.LogError("Invalid IP address provided: {IpAddress}", ipv4);
return Failed(HttpStatusCode.BadRequest, "Invalid IP address");
}
// Simulate work
await Task.Delay(5000, cancellationToken);
var location = "Amsterdam, Netherlands";
Logger.LogInformation("IP lookup result for {IpAddress}: {Location}", ipv4, location);
// Returning a succeeded state
return Succeeded(
result: new ResultResponse()
{
Messages = {
new ResultMessage()
{
Code = "200",
Message = $"Location info found for IP '{ipv4}'."
}
},
Outputs = {
new ResultOutput()
{
Name = "location",
Value = location
},
new ResultOutput()
{
Name = "coordinates",
Value = new { Latitude = 52.3730796, Longitude = 4.8924534 }
}
}
}
);
}
Control task behavior
In a default setup, serializable return types act as Succeeded() and exceptions as Failed(). By default - depending on the retry policy configured on the task - the task will be retried in case of failure.
When using the TaskCommandResult return type, it is possible to finetune the behavior.
The following return functions are available in both the WorkerController and the TaskuraiTaskContext:
- Succeeded: Mark the task as succeeded, the task is not retried.
- Failed: Mark the task as succeeded, either the task retry policy or the default retry policy applies (default = retry).
- IntermediateFailure: Mark the task as intermediate failure, either the task retry policy or the default retry policy applies (default = retry without limits).
- Fatal: Mark the task as failed permanently, the task will not be retried.
- Canceled: Mark the task as canceled, the task will not be retried.
Overriding retry delay on failed tasks
When using the TaskCommandResult return type, it is possible to override the retry delay on failed tasks by specifying the retryAfter option.
- C#
...
return IntermediateFailure(
statusCode: HttpStatusCode.TooManyRequests,
retryAfter: new After()
{
seconds = 60
});
...
Testing workers locally
- C#
Next, run the console application to validate the setup:
dotnet run
The newly created project should look like this:
Building...
[18:54:09 WRN] *** Taskurai.Worker Version: 2.5.17.0 ***
[18:54:09 WRN] *** Taskurai Version: 2.5.17.0 ***
[18:54:09 INF] Registered controller 'TestControllerSerializable' successfully.
[18:54:09 INF] Registered controller 'TestControllerTaskCommandResult' successfully.
Entering isolation mode, looking up token information...
Running in isolation mode (isolation key = 'dd10b001-addd-45c2-94a6-932282b7515c').
[18:54:10 INF] Discovered command 'GetIPLocationSerializable' in controller 'TestControllerSerializable'.
[18:54:10 INF] Discovered command 'UtcNowSerializable@1' in controller 'TestControllerSerializable'.
[18:54:10 INF] Discovered command 'GetIPLocationWithCommandResult' in controller 'TestControllerTaskCommandResult'.
[18:54:10 INF] Discovered command 'UtcNowWithCommandResult@1' in controller 'TestControllerTaskCommandResult'.
[18:54:10 INF] Discovered default command 'UtcNowSerializable' in controller 'TestControllerSerializable' (default or highest version = 1).
[18:54:10 INF] Discovered default command 'UtcNowWithCommandResult' in controller 'TestControllerTaskCommandResult' (default or highest version = 1).
[18:54:10 INF] Registering commands in Taskurai instance. (TaskuraiWorkerSDKVersion 2.5.17.0 - TaskuraiSDKVersion = 2.5.17.0)
[18:54:10 WRN] Taskurai worker TestWorker started.
[18:54:12 INF] Command succesfully registered for worker 'TestWorker'
[18:54:12 INF] Successfully registered command 'GetIPLocationSerializable' for worker 'TestWorker'
[18:54:12 INF] Successfully registered command 'UtcNowSerializable' for worker 'TestWorker'
[18:54:12 INF] Successfully registered command 'GetIPLocationWithCommandResult' for worker 'TestWorker'
[18:54:12 INF] Successfully registered command 'UtcNowWithCommandResult' for worker 'TestWorker'
The console application will run in Isolation Mode when the environment is running in Development mode.
In Isolation Mode, the tasks created and returned are isolated to your local session and will not affect workers running in the Taskurai instance.