PROS tasks are background workers β like having multiple control loops running at once. Use them when you need something to happen independent of opcontrol/auton.
flowchart TD
Create([pros::Task task_handle]) --> Args[Pass function + args
also priority + stack size]
Args --> Running[Task is running
repeats its body in a loop]
Running --> Q1{Need to pause?}
Q1 -->|"Yes"| Suspend[task_handle.suspend
task pauses, keeps state]
Q1 -->|"No, kill it"| Remove[task_handle.remove
task is destroyed]
Q1 -->|"Continue"| Running
Suspend --> Q2{Resume?}
Q2 -->|"Yes"| Resume[task_handle.resume
continues from where it paused]
Q2 -->|"No, kill it"| Remove
Resume --> Running
Remove --> Gone([Task destroyed
can't be resumed])
style Create fill:#1e293b,stroke:#22d3ee,stroke-width:2px,color:#e2e8f0
style Gone fill:#1e293b,stroke:#22c55e,stroke-width:2px,color:#e2e8f0
style Q1 fill:#fbbf24,color:#0f172a,stroke:#fbbf24
style Q2 fill:#fbbf24,color:#0f172a,stroke:#fbbf24
The bridge between inline concurrent actions and real task architecture. Run your intake, sensors, and autonomous simultaneously β without blocking your main loop.
A pros::Task is a separate thread of execution. It runs its own function loop in parallel with main. The PROS kernel schedules tasks based on priority and gives each a slice of CPU time every millisecond. You can have multiple tasks running simultaneously β PROS handles the switching.
// Task function β must be void and take void* void intakeTask(void* param) { while (true) { if (intakeWanted) { intake.move_velocity(600); } else { intake.brake(); } pros::delay(10); // ALWAYS delay β never spin-wait } } // Start the task β do this once in initialize() or autonomous() pros::Task myIntake(intakeTask);
// globals.hpp extern bool intakeOn; extern bool intakeReverse; pros::Mutex intakeMtx; // protects shared state // intake_task.cpp void intake_task_fn(void*) { while (true) { intakeMtx.take(TIMEOUT_MAX); bool on = intakeOn; bool rev = intakeReverse; intakeMtx.give(); if (on) { intake.move_velocity(rev ? -600 : 600); } else { intake.brake(); } pros::delay(10); } } // autonomous() β drive and intake run at the same time void autonomous() { pros::Task it(intake_task_fn); // Turn on intake, then immediately start driving intakeMtx.take(TIMEOUT_MAX); intakeOn = true; intakeMtx.give(); chassis.pid_drive_set(24, 110); chassis.pid_wait(); // intake runs during this wait intakeMtx.take(TIMEOUT_MAX); intakeOn = false; intakeMtx.give(); }
A mutex (mutual exclusion lock) prevents two tasks from reading and writing a shared variable at the same moment. Without one, you can get data corruption β your task reads a value halfway through main updating it.
intakeOn, targetPos)while(true) with a pros::delay(10) inside. Without the loop, the function exits and the task ends.pros::delay() in a task loop. Add pros::delay(10); as the last line of every task's while loop, always.intakeOn = true in autonomous but the intake task doesn't respond. Fix: Declare shared variables as volatile or use a mutex. The compiler may optimize away reads of non-volatile variables in tight loops.| Pattern | Use When |
|---|---|
| pid_wait_until() | You want to trigger something mid-movement (e.g., extend arm at 12 inches into a 24-inch drive) |
| pros::Task | A subsystem needs to run continuously regardless of what else is happening (intake, sensor logging, state machines) |
| pros::delay() in opcontrol | Reading controller inputs in the main opcontrol loop β 20ms delay gives 50Hz update rate |