FastLED 3.10.8
Loading...
Searching...
No Matches
task Directory Reference
+ Directory dependency graph for task:

Files

 _build.cpp.hpp
 Unity build header for fl/task/ directory.
 
 executor.cpp.hpp
 
 executor.h
 Task executor — runs registered task runners and manages the run loop.
 
 promise.h
 Promise-based fluent API for FastLED - standalone async primitives.
 
 promise_result.h
 
 scheduler.cpp.hpp
 
 scheduler.h
 Task scheduler — manages timer and frame-based tasks.
 
 task.cpp.hpp
 
 task.h
 

Detailed Description

A cooperative scheduler for sketches that need to do more than one thing at a time.

Instead of a loop() full of delay() calls and interleaved millis() comparisons, you register small callbacks that each own their own schedule. fl::task::run() executes whichever ones are due and returns. Adding a feature means adding a task — the existing tasks don't change.

Everything here is in namespace fl::task and is pulled in by #include <FastLED.h>.

Quick Start

#include <FastLED.h>
#include "fl/task/task.h"
#define NUM_LEDS 60
void setup() {
FastLED.addLeds<WS2812, 6>(leds, NUM_LEDS);
// Two blinkers on independent schedules
static bool on = false;
on = !on;
leds[0] = on ? CRGB::Red : CRGB::Black;
});
static bool on = false;
on = !on;
});
// One task owns the hardware push
fl::task::at_framerate(60).then([] { FastLED.show(); });
}
void loop() {
fl::task::run(); // run whatever is due, then return
}
CRGB leds[1]
void setup()
void loop()
#define NUM_LEDS
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
Handle & then(function< void()> on_then) FL_NO_EXCEPT
Sets the callback invoked when the task runs successfully.
Definition task.cpp.hpp:276
fl::CRGB CRGB
Definition crgb.h:25
Handle at_framerate(int fps) FL_NO_EXCEPT
Creates a task that runs at a fixed frame rate.
Definition task.cpp.hpp:328
void run(fl::u32 microseconds, ExecFlags flags) FL_NO_EXCEPT
Run selected task subsystems.
Handle every_ms(int interval_ms) FL_NO_EXCEPT
Creates a task that runs at a fixed millisecond interval.
Definition task.cpp.hpp:320
@ Red
<div style='background:#FF0000;width:4em;height:4em;'></div>
Definition crgb.h:629
@ Blue
<div style='background:#0000FF;width:4em;height:4em;'></div>
Definition crgb.h:519
@ Black
<div style='background:#000000;width:4em;height:4em;'></div>
Definition crgb.h:517

A runnable version of this is in examples/Async.

Why

The usual embedded tutorial teaches a single polling loop. That works until the second feature arrives. Once WiFi, MQTT, a button, and an animation all share one loop(), the timing of each is entangled with the others: a blocking read stalls the animation, and every new feature has to be woven through the code already there.

With tasks, a new feature is a new registration:

fl::task::every_ms(50).then([] { mqtt.poll(); });

Nothing above it changes.

fl::task::run() — the pump

run() must be called from loop() for time-based tasks. Frame tasks are the exception: before_frame() and after_frame() are pumped automatically by the FastLED frame lifecycle. If you never call run(), every_ms() and at_framerate() tasks never fire.

void loop() {
}
Call Effect
run() Pump everything, then yield for 1 ms (the default)
run(0) Pump everything, no yield
yield_system(250) OS-level yield only — for driver/DMA wait loops (same as run(250, ExecFlags::SYSTEM) without linking the Scheduler)
run(1000, ExecFlags::TASKS \| ExecFlags::COROUTINES) Pump tasks and coroutines, skip the OS yield

ExecFlags selects which subsystems get pumped:

The 1 ms default yield is what keeps a busy render loop from starving the network stack.

Some subsystems bump the scheduler on their own — the FX engine does so when an audio processor is attached, so audio samples are current for the frame being drawn. Treat that as an implementation detail: call run() yourself.

Creating tasks

Builder Fires
fl::task::every_ms(int interval_ms) Every interval_ms, forever
fl::task::at_framerate(int fps) fps times a second, forever
fl::task::before_frame() Before every FastLED render, until canceled
fl::task::after_frame() After every FastLED render, until canceled
fl::task::coroutine(const CoroutineConfig&) Once, on its own OS task (see below)

Each builder has an overload taking a TracePoint — pass the FL_TRACE macro and the scheduler can name the task in warnings, which is worth doing once you have more than a few:

fl::task::every_ms(100, FL_TRACE).then([] { /* ... */ });
#define FL_TRACE
A macro to capture the current source file, line number, and time.
Definition trace.h:102

The fluent API

auto handle = fl::task::every_ms(250, FL_TRACE)
.then([] { /* the work */ })
.catch_([](const fl::task::Error& e) { FL_WARN("task failed: " << e.message); });
#define FL_WARN(...)
Definition log.h:437
fl::string message
Definition promise.h:40
Error type for promises.
Definition promise.h:39

The returned Handle is a shared handle — copying it is cheap, and letting it go out of scope does not cancel the task, because the scheduler holds its own copy. Keep the handle only if you intend to .cancel() or re-time it later:

auto blink = fl::task::every_ms(500).then([] { toggle(); });
blink.set_interval_ms(100); // speed it up
blink.cancel(); // stop it
bool toggle
Definition Blur.ino:12

every_ms, at_framerate, before_frame, and after_frame tasks recur until canceled. Coroutines run once. Frame tasks do not require run(); engine frame events dispatch them automatically.

Callbacks run on whichever thread called run() or initiated the frame and are not preempted — keep each one short. A callback that blocks for 200 ms delays every other task by 200 ms.

Promises

Anything asynchronous returns a fl::task::Promise<T> with the same .then() / .catch_() shape:

#include "fl/net/http.h"
fl::net::http::fetch_get("http://fastled.io")
.then([](const fl::net::http::Response& resp) {
FL_WARN("got " << resp.text());
})
.catch_([](const fl::task::Error& err) {
FL_WARN("fetch failed: " << err.message);
});
const fl::string & text() const FL_NO_EXCEPT
Response body as text (like JavaScript response.text())
Definition fetch.h:95
HTTP response class (unified interface)
Definition fetch.h:78
fl::net::http — High-level HTTP client and server facade
fl::task::Promise< Response > fetch_get(const fl::string &url, const FetchOptions &request) FL_NO_EXCEPT
HTTP GET request.

The promise completes while the executor is pumped, so this still requires run() in loop(). You can also create and settle your own:

// ... later, from wherever the answer arrives:
p.complete_with_value(42);
static Promise< T > create() FL_NO_EXCEPT
Create a pending Promise.
Definition promise.h:61

Promise<T>::resolve(value) and Promise<T>::reject(error) build already-settled promises, which is handy for early returns and for tests.

await — sequential async

Callbacks are awkward when the logic is genuinely sequential: fetch, wait for the reply, then apply it. Both await forms give you that back, and both return a PromiseResult<T> — check ok() before value():

auto result = fl::task::await_top_level(fl::net::http::fetch_get("http://fastled.io"));
if (result.ok()) {
apply(result.value().text());
} else {
FL_WARN("fetch failed: " << result.error().message);
}
PromiseResult< T > await_top_level(Promise< T > p) FL_NO_EXCEPT
Synchronously wait for a promise to complete (ONLY safe in top-level contexts)
Definition executor.h:193
Where Cost
await_top_level(p) Arduino loop() / main(), every platform Busy-waits, calling run(1000) in a loop — other tasks still run, but the CPU is spinning
await(p) Inside a coroutine — ESP32 and host builds Blocks that coroutine only, at zero CPU cost; everything else keeps running

Prefer await() when you have a coroutine to put the work on. await_top_level() refuses to nest more than a few levels deep and gives up rather than hanging forever.

Coroutines

A coroutine is a real OS task (a FreeRTOS task on ESP32, a std::thread on host builds), so it can block on await() without stalling the animation.

#include "fl/net/http.h"
auto worker = fl::task::coroutine({
.func = [] {
auto r = fl::task::await(fl::net::http::fetch_get("http://my-server/color"));
if (r.ok()) {
apply_color(r.value().text());
}
},
.name = "color-fetch",
});
Handle coroutine(const CoroutineConfig &config) FL_NO_EXCEPT
Creates and starts an OS-level coroutine task.
Definition task.cpp.hpp:364
PromiseResult< T > await(Promise< T > p, fl::u32 timeout_ms=fl::platforms::kAwaitDefaultTimeoutMs) FL_NO_EXCEPT
Await promise completion in a coroutine (Trampoline to platform implementation)
Definition executor.h:316

CoroutineConfig fields:

Field Default Notes
func — The body. Required.
name "task" Shows up in RTOS task listings
stack_size 4096 Bytes
priority 5 RTOS priority
core_id unset Pin to a CPU core on multi-core parts
trace unset FL_TRACE for diagnostics

The handle is RAII: worker.stop() ends it, and so does letting the handle go out of scope. From inside the body, fl::task::exit_current() self-deletes.

Platform support

Scheduler (every_ms, at_framerate) await_top_level coroutine() + await
AVR / small MCUs ✅ ✅ ❌ no-op
ESP32 family ✅ ✅ ✅ FreeRTOS task
Teensy 4.x ✅ ✅ ✅
Other Arduino / ARM ✅ ✅ ❌ no-op
Host / stub (tests) ✅ ✅ ✅ std::thread
WASM ✅ ✅ ✅

Code using coroutines still compiles everywhere; where there is no support, fl::task::coroutine() is a no-op, so don't depend on the body having run.

Gotchas

Files

File Contents
task.h Handle, the builders, CoroutineConfig
scheduler.h Scheduler — task storage and dispatch
executor.h run(), ExecFlags, Runner, await, await_top_level
promise.h Promise<T>
promise_result.h PromiseResult<T>, Error

See also