When is it recommended to use tasks?

I developed several applications with ESP32, using the Arduino Framework, following the usual single-task approach, i.e. all the code inside the loop() function, even if organized in classes.

Recently, I had to wrote few applications using the ESP-IDF Framework and i learnt about the tasks.

Hence I wonder if it might make sense to use tasks also with the Arduino framework (I'm always talking for ESP32!).

What advantages would it have? Would there be improvements about the RAM usage (stack/heap) for large projects? Or could the downsides and potential issues increase?

Using tasks on an ESP32 in the Arduino world allows for multitasking, modularity, possible improved responsiveness, with different resource management (each task gets its stack), and true concurrency (if you use both cores and as long as tasks can switch).

However, developing multithreaded apps when you have dependencies between the tasks adds complexity, increases memory overhead, introduces the risk of deadlocks and race conditions (study mutex / semaphores), poses debugging challenges, and incurs context switching overhead.

The OS also uses ancillary tasks for wifi and other system resources so using both cores would get in competition with those tasks and care should be taken not to disrupt those. The watchdog might come barking from time to time otherwise.

1 Like

I can't imagine a real scenario where different tasks don't depend from each other.
For example a common use-case for my applications is:

  • user input: buttons, remote IR
  • network: HTTP server
  • outputs: display, stepper motor, NeoPixel LED chain, UARTs

Currently I have all the stuff running in the usual "loop" task. The application is quite large (~80 kB RAM, ~ 2MB flash). Personally I don't use any malloc() but I can't be sure about the libraries used (olikraus/U8g2, crankyoldgit/IRremoteESP8266, makuna/NeoPixelBus, rlogiacco/CircularBuffer, pablo-sampaio/Easy MFRC522, me-no-dev/ESPAsyncWebServer.git, guestisp/ESP32AsyncDNSServer.git, adafruit/Adafruit BusIO, bblanchon/ArduinoJson, ricmoo/QRCode, thomasfredericks/Bounce2, sparkfun/SparkFun PCA9536 Arduino Library, ayushsharma82/ElegantOTA).

I was tempted to split some of the code into different tasks, i.e.:

  • user inputs
  • display
  • stepper motor

leaving the FSM that handles the overall logic in the main loop. Of course each task has to exchange data with the others. I.e. the motor task would receive the commands for moving, and would send back its current position (both with polling or when reaching the target).

Why I'm going to evaluate such a migration? During the development I stumbled across some crashes and I discovered it may be caused by the String usage (I try to avoid it, but sometime I really cannot get rid them totally!) and array size.

I'm surely made mistakes in the RAM handling, but assigning a stack for each task perhaps it can help me to easily spot the issues. I'm not interested in a true concurrency (even for the stepper motor, it's enough to have a reliable call each X milliseconds).

1 Like

On your computer when you use your web browser and a text editor they don't depend on each other much, right ? So same could apply to some "program" on your ESP32, for example you have to manage a number of identical machines but each machine is independent from the other.

That being said, Yes, your common use case makes sense too.

ArduinoJson in its latest version went for dynamic allocation - see post by Benoît here


Your split can make sense. If you run them on the same core it won't be much different than running everything in the looptask itself from a user perspective (still only one core handling everything sequentially).

You should get to the bottom of that. Your task based design probably won't solve it as although stack size is limited, Strings do not allocate their buffer there, it goes into the heap. So if you are poking holes into the heap or requesting too much memory then the issue will stil be there.

just so that you know, the loop function is actually a task launched by the (hidden) main.cpp that's added to your sketch when you compile (and you could change the stack size)

1 Like

Multitasking is a pretty elegant way to run different tasks that normally take a longer time to execute. If, for example, all that a web server needs to do is occasionally send short replies it wouldn't make sense to use many simultaneous tasks to do the job. But if you want to have several websocket handled simultaneously, running for, say 5 minutes, multitasking is a way to go. In this case every websocket would be more or less independent from another.

What you have to take care of is that all functions you write or use should be reentrant. If they use only local variables they are automatically reentrant. But if they use global variables a special consideration must be taken so that one task does not corrupt what the other is doing, as J-M-LJackson pointed out.

Besides you must avoid calling non-reentrant C functions and call reentrant version of them instead. Basically, every C function that provides a buffer is not-reentrant, like:

tm* gmtime ( const time_t* current_time )

You may want to use:

struct tm *gmtime_r( const time_t *timer, struct tm *buf );

instead. You may also want to take a look at shared memory, semaphores, mutexes, dead-locks for inter process synchronization.

1 Like

someone suggested multi-tasking is a social management tool. individuals do work within their task.

of course there are many ways tasks can be used. i've been surprised that people found it difficult to understand that i didn't need an OS of some sort for the embedded work i've done

Considering just using a single ESP32 core, you could, of course, accomplish pretty much the same ends using the standard millis-based “Arduino Several Things at a Time” technique.

Multi-Tasking in the FreeRTOS environment is a different programming paradigm that requires different thinking about code organization.

I prefer to write code this way when possible (i.e. when coding for an ESP32) as I like the clear modularity and stucture it provides. So, IMO, it comes down to your preference.

As I mentioned FreeRTOS programming is a completely different programming paradigm. IMO, you ether embrace and use it fully or your don't. I would consider a "half-way" approach such as this to be the worst of both worlds.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.