I’ve seen teams use 254 Loopers and notifiers, and I know they help running code at higher frequencies. However, I’ve read WPILIB documentation and seen that there is the AddPeriodic() function which does a similar thing, but helps with eliminating potential thread safety issues.
My question is, which option is best and more recommended for teams?
User input things should be run with a 20 ms period because Driver Station packets arrive in 20 ms intervals. Using a lower period won’t make any difference in responsiveness.
The lowest consistent period for feedback controllers on the roboRIO is 10 ms for Java (due in part to garbage collection) and 5 ms for C++. You’d mainly need lower periods for systems that accelerate a lot within one timestep (e.g., a flywheel). It doesn’t matter as much for a drivetrain.
Use addPeriodic(); it’s a more modern alternative to 254’s Looper thing. In general, 254 has a lot of legacy code they haven’t replaced because “it works, so why change it when we could spend our time doing other things”.
Cooperative multitasking is preferred over Notifiers in FRC because:
The roboRIO only has two cores anyway. That means multiple Notifiers are rarely ever truly running in parallel as opposed to one’s callback being preempted by another via the Linux scheduler.
With cooperative multitasking, you have more control over how and when the previously mentioned preemption occurs, so you can reason about the correctness of your code. Cooperative multitasking is easier to reason about and write correctly than multi-threaded code.
Please keep this in mind when reading code from “top-tier” teams. WPILib changes faster than some team libraries, and many top teams are still using custom implementations of things that WPILib now provides standard. This is not necessarily because those custom implementations are better than what WPILib offers, but instead because they existed first and there’s a nontrivial cost to switch over.
So, for this last year, we choose to make a new thread for reading the color sensor. The sensor would do I2C reads to get all 3 colors every time you called it, and some time those reads would lag. Making it a separate thread made our main code stop overrunning randomly (less). (this could have been the I2C lockup issue also, we never got clarification if it was random slowdown spikes, like 500ms, or just shut down the rio)
So, for next season, should we have used addPeriodic() instead? Would random slowdowns throw off our timing?
Also, if this is the proper way to schedule background tasks, why does some of the WpiLib tasks (I’m looking at DataLogManager) spin off threads instead of using addPeriodic()?
Threads are indeed the correct way to handle tasks that may have long processing that can’t be broken into small steps (e.g. image processing using OpenCV) or block on I/O (e.g. I2C reads, DataLog writing to disk). Cooperative multitasking in the style of addPeriodic() only works with non-blocking operations that return as quickly as possible (that’s the “cooperative” part).
In general the WPILib API design tries to hide threads (if they’re needed) behind non-blocking interfaces–this is the case for DataLog and NetworkTables. The lower level direct protocol layers like I2C are a notable exception (because there’s no way to implement a non-blocking low-level read). Sensors that use I2C would ideally use a background thread to actually perform the I2C blocking reads and present a non-blocking high-level API to the user (e.g. for the last read sensor value).