Skip to main content

Creating commands

Tasks in Taskurai execute commands. Commands accept a task payload with arguments, custom payload, and various settings.

Commands should be defined in workers. A worker can contain one or more commands.

Command names should be unique (case insensitive) within a Taskurai installation.

Prerequisites

Sample command

You should change the included command to the desired outcome.

Commands/TestCommand.cs
using Taskurai.Models;
using Taskurai.Worker;

public class TestCommand : TaskuraiCommand
{
public override async Task ExecuteAsync(TaskuraiTaskContext context, CancellationToken cancellationToken)
{
// Execute the command...

var completeInput = new CompleteTaskInput()
{
Status = TaskStatusType.Succeeded,
StatusCode = 200
};

await context.CompleteTaskAsync(completeInput);
}
}

Commands are registered in the Program.cs file like this:

Program.cs
using Taskurai.Worker;

await TaskuraiWorkerSetup.CreateDefaultBuilder(args)
.ConfigureHost((options) =>
{
})
.Commands((commands) =>
{
commands
.Command<TestCommand>("testCommand")
;
})
.RunAsync();

Adding extra commands

To add a new command:

  1. Create a new command in the Commands folder:
Commands/ExtraCommand.cs
using Taskurai.Models;
using Taskurai.Worker;

public class ExtraCommand : TaskuraiCommand
{
public override async Task ExecuteAsync(TaskuraiTaskContext context, CancellationToken cancellationToken)
{
// Execute the command...

var completeInput = new CompleteTaskInput()
{
Status = TaskStatusType.Succeeded,
StatusCode = 200
};

await context.CompleteTaskAsync(completeInput);
}
}
  1. Register the command in Program.cs:
Program.cs
using Taskurai.Worker;

await TaskuraiWorkerSetup.CreateDefaultBuilder(args)
.ConfigureHost((options) =>
{
})
.Commands((commands) =>
{
commands
.Command<TestCommand>("testCommand")
.Command<ExtraCommand>("extraCommand") // Register new command
;
})
.RunAsync();

Accessing the task context

Workers have convenient access to the task context:

Commands/ExtraCommand.cs
using Taskurai.Models;
using Taskurai.Worker;

public class TestCommand : TaskuraiCommand
{
public override async Task ExecuteAsync(TaskuraiTaskContext context, CancellationToken cancellationToken)
{
// Extend the task visibility timeout to other worker instances
var extendResponse = await context.ExtendTaskAsync(300);

// Log task information
var task = context.Task;

Logger.LogInformation($"Handling task {task.Id} - {task?.Config?.Command}...");

// Do some work...

var completeInput = new CompleteTaskInput()
{
Status = TaskStatusType.Succeeded,
StatusCode = 200
};

await context.CompleteTaskAsync(completeInput);
}
}

Visibility timeout

Workers can be set up to scale horizontally. A worker can also be configured to handle multiple tasks in parallel.

Each time a worker receives a new task it can handle, the task is marked invisible to other instances of the worker.

This can be configured in appsettings.json

appsettings.json
{
...
"Taskurai": {
"VisibilityTimeout": 300, // Timeout in seconds (default 300, max 7 days)
}
}
...

When a worker fails to complete a task - without reporting the completion status - the task will reappear in the queue, ready to be picked up by another instance.

Extend visibility timeout

It is possible to extend the visibility timeout while processing a job multiple times:

// Extend the task visibility timeout to other worker instances
var extendResponse = await context.ExtendTaskAsync(300); // 300 seconds

Reporting progress and heartbeat

Sending a heartbeat event

While running a job, it is recommended to send heartbeat events for long running tasks.

await context.ProgressAsync();

Reporting progress and sending a heartbeat event

While running a job, it is possible to report on progress and send a heartbeat event and the same time.

await context.ProgressAsync(
progress: 10,
message: $"Requesting ocr for invoice {upload.Id}...");

Completing a task

Once the task is finished, the final state can be stored for the task:

// Custom data
var ocrResult = new InvoiceOcrResult()
{
InvoiceId = uploadId,
OutputFileName = ocrOutputFileName
};

var completeInput = new CompleteTaskInput()
{
Status = TaskStatusType.Succeeded,
StatusCode = 200,
Progress = new TaskProgress()
{
Progress = 100.0,
Message = "Task finished."
},
Result = new TaskResult()
{
Summary = $"OCR for upload {uploadId} has been successfully stored at {ocrOutputFileName}.",
Data = BinaryData.FromObjectAsJson(ocrResult)
}
};

await context.CompleteTaskAsync(completeInput);