In this page
Tasks
Tasks
GreyCat comes with a concept of asynchronous tasks built-in. The mental model for the tasks is close to what other languages have:
- Java’s
Future
- JavaScript’s
Promise
They essentially are a handle to a potentially not-yet-done computation. Allowing you to poll for readiness, or cancel it mid-way. Tasks are well-suited for long calculations using the graph or involving network requests.
From a language perspective, there are no differences between defining a task or a function.
Executing a Task
To execute a task remotely, add task as a key to the Header when making a request, this will return an instance of a runtime::Task
containing all the relevant meta information about the spawned task.
curl -H "task:''" -X POST -d '[]' http://localhost:8080/project::long_computation
# Task{user_id:1,task_id:1,mod:"tasks",type:null,fun:"long_computation",creation:'2024-06-24T10:23:47.965517+00:00',start:null,duration:null,status:TaskStatus::waiting}
Task Object
Field | Type | Description |
---|---|---|
user_id | int |
The id of the user that spawned the task |
task_id | int |
The unique id of the spawned task |
mod | String ? |
The Task Mode |
fun | String ? |
The name of the function spawned |
creation | time |
Time when the task was spawned |
start | time ? |
Time when the task started execution |
duration | duration ? |
How long the task took |
status | TaskStatus |
The status of the task |
To follow the task status call runtime::Task::info with the tasks user and task id
curl -X POST -d '[1,1]' http://localhost:8080/runtime::Task::info
# TaskInfo{user_id:1,task_id:1,mod:"tasks",type:null,fun:"long_computation",creation:'2024-06-24T10:15:18.814828+00:00',start:'2024-06-24T10:15:18.815025+00:00',duration:8ms278us,status:TaskStatus::ended,progress:1.0,remaining:null,sub_waiting:0,sub_tasks_all:0}
TaskInfo
In addition to the Task
fields the TaskInfo
also contains additional information
Field | Type | Description |
---|---|---|
progress | float? | The current task progress, can be updated by the user manually |
remaining | duration? | Based on the progress will estimate how much time is remaining |
sub_waiting | int? | How many subtasks are waiting to be executed |
sub_tasks_all | int? | How many subtasks where spawned in total |
Task Status
To track the state of your Task the response contains an TaskStatus
enum that explains the different stages it can be in
Enum Value | Description |
---|---|
empty | Task is empty |
waiting | Task is waiting |
running | Task is running |
await | Task is awaiting |
cancelled | Task is cancelled |
error | Task encountered error |
ended | Task ended successfully |
ended_with_errors | Task ended with errors |
Retrieve Task result
In the above example after fetching the task info, the TaskStatus
is set to ended which means we can retrieve the result which is stored in an result.gcb file
The numbers after files and tasks are the respective user and task id that you would replace with the ones from the TaskInfo
curl -X GET 'http://localhost:8080/files/0/tasks/1/result.gcb?json'
Periodic tasks
How it works
Periodic tasks are like Tasks but as the name implies GreyCat will run them periodically once registered.
To register periodic tasks we need to use the runtime
module:
fn my_task() {
println("The current time is ${time::now()}");
}
fn main() {
var my_task_every_day = PeriodicTask {
user_id: 0, // the user associated with the execution
arguments: null, // the arguments to use for the execution
every: 1_day, // the periodicity as a duration
function: project::my_task, // the function pointer of the task
start: time::now(), // the time of the first execution
};
// register the task in the scheduler
PeriodicTask::set(Array<PeriodicTask>{my_task_every_day});
}
Note that as of today, the scheduler API is only providing
PeriodicTask::set(...)
which means you have to always define the whole list of periodic task when callingPeriodicTask::set(...)
otherwise the previously registered ones will be unregistered.
Manipulating periodic tasks
Because the API is minimalist, manipulating the current list of registered PeriodicTask
s is a bit tedious so here is an example of abstraction above what is currently available in the standard library:
type PeriodicTaskHelper {
/// Schedules the given task
static fn schedule(pTask: PeriodicTask) {
var tasks = PeriodicTask::all();
tasks.add(pTask);
PeriodicTask::set(tasks);
}
/// Removes the first PeriodicTask from the scheduler and returns it
///
/// If there is no tasks scheduled, `null` is returned.
static fn shift(): PeriodicTask? {
var tasks = PeriodicTask::all();
var pTask: PeriodicTask?;
if (tasks.size() > 0) {
pTask = tasks[0];
tasks.remove(0);
PeriodicTask::set(tasks);
}
return pTask;
}
/// Removes the last PeriodicTask from the scheduler and returns it
///
/// If there is no tasks scheduled, `null` is returned.
static fn pop(): PeriodicTask? {
var tasks = PeriodicTask::all();
var len = tasks.size();
var pTask: PeriodicTask?;
if (len > 0) {
pTask = tasks[len];
tasks.remove(len);
PeriodicTask::set(tasks);
}
return pTask;
}
/// Removes the task at the given index.
///
/// Returns `true` on success, otherwise `false`
static fn remove_at(index: int): bool {
var tasks = PeriodicTask::all();
var len = tasks.size();
if (index >= len) {
return false;
}
tasks.remove(index);
PeriodicTask::set(tasks);
return true;
}
/// Removes the first task that matches `task.function == ptr`
///
/// Returns `true` on success, otherwise `false`
static fn remove_first(ptr: function): bool {
var tasks = PeriodicTask::all();
for (i, pTask in tasks) {
if (pTask.function == ptr) {
tasks.remove(i);
PeriodicTask::set(tasks);
return true;
}
}
return false;
}
/// Removes the last task that matches `task.function == ptr`
///
/// Returns `true` on success, otherwise `false`
static fn remove_last(ptr: function): bool {
var tasks = PeriodicTask::all();
var len = tasks.size();
for (var i = len - 1; i >= 0; i--) {
if (tasks[i].function == ptr) {
tasks.remove(i);
PeriodicTask::set(tasks);
return true;
}
}
return false;
}
}