Просмотр исходного кода

v3.6.0 - _TASK_THREAD_SAFE compile option for running under RTOS

Anatoli Arkhipenko 4 лет назад
Родитель
Сommit
635cfd5bcc
6 измененных файлов с 420 добавлено и 224 удалено
  1. 2 1
      README.md
  2. 3 0
      keywords.txt
  3. 1 1
      library.json
  4. 1 1
      library.properties
  5. 398 211
      src/TaskScheduler.h
  6. 15 10
      src/TaskSchedulerDeclarations.h

+ 2 - 1
README.md

@@ -1,6 +1,6 @@
 # Task Scheduler
 ### Cooperative multitasking for Arduino, ESPx, STM32 and other microcontrollers
-#### Version 3.5.0: 2021-11-01 [Latest updates](https://github.com/arkhipenko/TaskScheduler/wiki/Latest-Updates)
+#### Version 3.6.0: 2021-12-17 [Latest updates](https://github.com/arkhipenko/TaskScheduler/wiki/Latest-Updates)
 
 [![arduino-library-badge](https://www.ardu-badge.com/badge/TaskScheduler.svg?)](https://www.ardu-badge.com/TaskScheduler)[![xscode](https://img.shields.io/badge/Available%20on-xs%3Acode-blue?style=?style=plastic&logo=appveyor&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF////////VXz1bAAAAAJ0Uk5T/wDltzBKAAAAlUlEQVR42uzXSwqAMAwE0Mn9L+3Ggtgkk35QwcnSJo9S+yGwM9DCooCbgn4YrJ4CIPUcQF7/XSBbx2TEz4sAZ2q1RAECBAiYBlCtvwN+KiYAlG7UDGj59MViT9hOwEqAhYCtAsUZvL6I6W8c2wcbd+LIWSCHSTeSAAECngN4xxIDSK9f4B9t377Wd7H5Nt7/Xz8eAgwAvesLRjYYPuUAAAAASUVORK5CYII=)](https://xscode.com/arkhipenko/TaskScheduler)
 
@@ -34,6 +34,7 @@ _“Everybody who learns concurrency and thinks they understand it, ends up find
 13. CPU load / idle statistics for time critical applications
 14. Scheduling options with priority for original schedule (with and without catchup) and interval
 15. Ability to pause/resume and enable/disable scheduling
+15. Thread-safe scheduling while running under preemptive scheduler (i. e., FreeRTOS)
 
 Scheduling overhead: between `15` and `18` microseconds per scheduling pass (Arduino UNO rev 3 @ `16MHz` clock, single scheduler w/o prioritization)
 

+ 3 - 0
keywords.txt

@@ -114,6 +114,9 @@ _TASK_WDT_IDS	LITERAL1
 _TASK_EXPOSE_CHAIN	LITERAL1
 _TASK_DEFINE_MILLIS	LITERAL1
 _TASK_SCHEDULING_OPTIONS	LITERAL1
+_TASK_DEFINE_MILLIS	LITERAL1
+_TASK_EXTERNAL_TIME	LITERAL1
+_TASK_THREAD_SAFE	LITERAL1
 SleepCallback	LITERAL1
 TASK_FOREVER	LITERAL1
 TASK_HOUR	LITERAL1

+ 1 - 1
library.json

@@ -16,7 +16,7 @@
       "maintainer": true
     }
   ],
-  "version": "3.5.0",
+  "version": "3.6.0",
   "frameworks": "arduino",
   "platforms": "*"
 }

+ 1 - 1
library.properties

@@ -1,5 +1,5 @@
 name=TaskScheduler
-version=3.5.0
+version=3.6.0
 author=Anatoli Arkhipenko <arkhipenko@hotmail.com>
 maintainer=Anatoli Arkhipenko <arkhipenko@hotmail.com>
 sentence=Cooperative multitasking for Arduino, ESPx, STM32 and other microcontrollers.

+ 398 - 211
src/TaskScheduler.h

@@ -1,212 +1,216 @@
-// Cooperative multitasking library for Arduino
-// Copyright (c) 2015-2019 Anatoli Arkhipenko
-//
-// Changelog:
-// v1.0.0:
-//     2015-02-24 - Initial release
-//     2015-02-28 - added delay() and disableOnLastIteration() methods
-//     2015-03-25 - changed scheduler execute() method for a more precise delay calculation:
-//                  1. Do not delay if any of the tasks ran (making request for immediate execution redundant)
-//                  2. Delay is invoked only if none of the tasks ran
-//                  3. Delay is based on the min anticipated wait until next task _AND_ the runtime of execute method itself.
-//     2015-05-11 - added  restart() and restartDelayed() methods to restart tasks which are on hold after running all iterations
-//     2015-05-19 - completely removed  delay from the scheduler since there are no power saving there. using 1 ms sleep instead
-//
-// v1.4.1:
-//     2015-09-15 - more careful placement of AVR-specific includes for sleep method (compatibility with DUE)
-//                  sleep on idle run is no longer a default and should be explicitly compiled with
-//                 _TASK_SLEEP_ON_IDLE_RUN defined
-//
-// v1.5.0:
-//     2015-09-20 - access to currently executing task (for callback methods)
-//     2015-09-20 - pass scheduler as a parameter to the task constructor to append the task to the end of the chain
-//     2015-09-20 - option to create a task already enabled
-//
-// v1.5.1:
-//     2015-09-21 - bug fix: incorrect handling of active tasks via set() and setIterations().
-//                  Thanks to Hannes Morgenstern for catching this one
-//
-// v1.6.0:
-//     2015-09-22 - revert back to having all tasks disable on last iteration.
-//     2015-09-22 - deprecated disableOnLastIteration method as a result
-//     2015-09-22 - created a separate branch 'disable-on-last-iteration' for this
-//     2015-10-01 - made version numbers semver compliant (documentation only)
-//
-// v1.7.0:
-//    2015-10-08 - introduced callback run counter - callback methods can branch on the iteration number.
-//    2015-10-11 - enableIfNot() - enable a task only if it is not already enabled. Returns true if was already enabled,
-//                 false if was disabled.
-//    2015-10-11 - disable() returns previous enable state (true if was enabled, false if was already disabled)
-//    2015-10-11 - introduced callback methods "on enable" and "on disable". On enable runs every time enable is called,
-//                 on disable runs only if task was enabled
-//    2015-10-12 - new Task method: forceNextIteration() - makes next iteration happen immediately during the next pass
-//                 regardless how much time is left
-//
-// v1.8.0:
-//    2015-10-13 - support for status request objects allowing tasks waiting on requests
-//    2015-10-13 - moved to a single header file to allow compilation control via #defines from the main sketch
-//
-// v1.8.1:
-//    2015-10-22 - implement Task id and control points to support identification of failure points for watchdog timer logging
-//
-// v1.8.2:
-//    2015-10-27 - implement Local Task Storage Pointer (allow use of same callback code for different tasks)
-//    2015-10-27 - bug: currentTask() method returns incorrect Task reference if called within OnEnable and OnDisable methods
-//    2015-10-27 - protection against infinite loop in OnEnable (if enable() methods are called within OnEnable)
-//    2015-10-29 - new currentLts() method in the scheduler class returns current task's LTS pointer in one call
-//
-// v1.8.3:
-//    2015-11-05 - support for task activation on a status request with arbitrary interval and number of iterations
-//                (0 and 1 are still default values)
-//    2015-11-05 - implement waitForDelayed() method to allow task activation on the status request completion
-//                 delayed for one current interval
-//    2015-11-09 - added callback methods prototypes to all examples for Arduino IDE 1.6.6 compatibility
-//    2015-11-14 - added several constants to be used as task parameters for readability (e.g, TASK_FOREVER, TASK_SECOND, etc.)
-//    2015-11-14 - significant optimization of the scheduler's execute loop, including millis() rollover fix option
-//
-// v1.8.4:
-//    2015-11-15 - bug fix: Task alignment with millis() for scheduling purposes should be done after OnEnable, not before.
-//                 Especially since OnEnable method can change the interval
-//    2015-11-16 - further optimizations of the task scheduler execute loop
-//
-// v1.8.5:
-//    2015-11-23 - bug fix: incorrect calculation of next task invocation in case callback changed the interval
-//    2015-11-23 - bug fix: Task::set() method calls setInterval() explicitly, therefore delaying the task in the same manner
-//
-// v1.9.0:
-//    2015-11-24 - packed three byte-long status variables into bit array structure data type - saving 2 bytes per each task instance
-//
-// v1.9.2:
-//    2015-11-28 - _TASK_ROLLOVER_FIX is deprecated (not necessary)
-//    2015-12-16 - bug fixes: automatic millis rollover support for delay methods
-//    2015-12-17 - new method for _TASK_TIMECRITICAL option: getStartDelay()
-//
-// v2.0.0:
-//    2015-12-22 - _TASK_PRIORITY - support for layered task prioritization
-//
-// v2.0.1:
-//    2016-01-02 - bug fix: issue#11 Xtensa compiler (esp8266): Declaration of constructor does not match implementation
-//
-// v2.0.2:
-//    2016-01-05 - bug fix: time constants wrapped inside compile option
-//    2016-01-05 - support for ESP8266 wifi power saving mode for _TASK_SLEEP_ON_IDLE_RUN compile option
-//
-// v2.1.0:
-//    2016-02-01 - support for microsecond resolution
-//    2016-02-02 - added Scheduler baseline start time reset method: startNow()
-//
-// v2.2.0:
-//    2016-11-17 - all methods made 'inline' to support inclusion of TaskSchedule.h file into other header files
-//
-// v2.2.1:
-//    2016-11-30 - inlined constructors. Added "yield()" and "yieldOnce()" functions to easily break down and chain
-//                 back together long running callback methods
-//    2016-12-16 - added "getCount()" to StatusRequest objects, made every task StatusRequest enabled.
-//                 Internal StatusRequest objects are accessible via "getInternalStatusRequest()" method.
-//
-// v2.3.0:
-//    2017-02-24 - new timeUntilNextIteration() method within Scheduler class - inquire when a particlar task is
-//                 scheduled to run next time
-//
-// v2.4.0:
-//    2017-04-27 - added destructor to the Task class to ensure tasks are disables and taken off the execution chain
-//                 upon destruction. (Contributed by Edwin van Leeuwen [BlackEdder - https://github.com/BlackEdder)
-//
-// v2.5.0:
-//    2017-04-27 - ESP8266 ONLY: added optional support for std::functions via _TASK_STD_FUNCTION compilation option
-//                 (Contributed by Edwin van Leeuwen [BlackEdder - https://github.com/BlackEdder)
-//    2017-08-30 - add _TASK_DEBUG making all methods and variables public FOR DEBUGGING PURPOSES ONLY!
-//                 Use at your own risk!
-//    2017-08-30 - bug fix: Scheduler::addTask() checks if task is already part of an execution chain (github issue #37)
-//    2017-08-30 - support for multi-tab sketches (Contributed by Adam Ryczkowski - https://github.com/adamryczkowski)
-//
-// v2.5.1:
-//    2018-01-06 - support for IDLE sleep on Teensy boards (tested on Teensy 3.5)
-//
-// v2.5.2:
-//    2018-01-09 - _TASK_INLINE compilation directive making all methods declared "inline" (issue #42)
-//
-// v2.6.0:
-//    2018-01-30 - _TASK_TIMEOUT compilation directive: Task overall timeout functionality
-//    2018-01-30 - ESP32 support (experimental)
-//                 (Contributed by Marco Tombesi: https://github.com/baggior)
-//
-// v2.6.1:
-//    2018-02-13 - Bug: support for task self-destruction in the OnDisable method
-//                 Example 19: dynamic tasks creation and destruction
-//    2018-03-14 - Bug: high level scheduler ignored if lower level chain is empty
-//                 Example 20: use of local task storage to work with task-specific class objects
-//
-// v3.0.0:
-//    2018-03-15 - Major Release: Support for dynamic callback methods binding via compilation parameter _TASK_OO_CALLBACKS
-//
-// v3.0.1:
-//    2018-11-09 - bug: task deleted from the execution chain cannot be added back (github issue #67)
-//
-// v3.0.2:
-//    2018-11-11 - bug: default constructor is ambiguous when Status Request objects are enabled (github issue #65 & #68)
-//
-// v3.0.3:
-//    2019-06-13 - feature: custom sleep callback method: setSleepMethod() - ability to dynamically control idle sleep for various microcontrollers
-//               - feature: support for MSP430 and MSP432 boards (pull request #75: big thanks to Guillaume Pirou, https://github.com/elominp)
-//               - officially discontinued support for offile documentation in favor of updating the Wiki pages
-//
-// v3.1.0:
-//    2020-01-07 - feature: added 4 cpu load monitoring methods for _TASK_TIMECRITICAL compilation option
-//
-// v3.1.1:
-//    2020-01-09 - update: more precise CPU load measuring. Ability to define idle sleep threshold for ESP chips
-//
-// v3.1.2:
-//    2020-01-17 - bug fix: corrected external forward definitions of millis() and micros
-// 
-// v3.1.3:
-//    2020-01-30 - bug fix: _TASK_DEFINE_MILLIS to force forward definition of millis and micros. Not defined by default. 
-//    2020-02-16 - bug fix: add 'virtual' to the Task destructor definition (issue #86)
-//
-// v3.1.4:
-//    2020-02-22 - bug: get rid of unnecessary compiler warnings
-//    2020-02-22 - feature: access to the task chain with _TASK_EXPOSE_CHAIN compile option
-//
-// v3.1.5:
-//    2020-05-08 - feature: implemented light sleep for esp32
-//
-// v3.1.6:
-//    2020-05-12 - bug fix: deleteTask and addTask should check task ownership first (Issue #97)
-//
-// v3.1.7:
-//    2020-07-07 - warning fix: unused parameter 'aRecursive' (Issue #99)
-//
-// v3.2.0:
-//    2020-08-16 - feature: scheduling options
-//
-// v3.2.1:
-//    2020-10-04 - feature: Task.abort method. Stop task execution without calling OnDisable(). 
-//
-// v3.2.2:
-//    2020-12-14 - feature: enable and restart methods return true if task enabled 
-//                 feature: Task.cancel() method - disable task with a cancel flag (could be used for alt. path
-//                          processing in the onDisable method.
-//                 feature: Task.cancelled() method - indicates that task was disabled with a cancel() method.
-//
-// v3.2.3:
-//    2021-01-01 - feature: discontinued use of 'register' keyword. Depricated in C++ 11 
-//                 feature: add STM32 as a platform supporting _TASK_STD_FUNCTION. (PR #105)
-//
-// v3.3.0:
-//    2021-05-11 - feature: Timeout() methods for StatusRequest objects 
-//
-// v3.4.0:
-//    2021-07-14 - feature: ability to Enable/Disable and Pause/Resume scheduling 
-//               - feature: optional use of external millis/micros methods 
-//
-// v3.5.0:
-//    2021-11-01 - feature: adjust(long aInterval) method - adjust execution schedule: 
-//                 + aInterval - shift schedule forward (later)
-//                 - aInterval - shift schedule backwards (earlier)
-//
-//
+/*
+Cooperative multitasking library for Arduino
+Copyright (c) 2015-2019 Anatoli Arkhipenko
+
+Changelog:
+v1.0.0:
+    2015-02-24 - Initial release
+    2015-02-28 - added delay() and disableOnLastIteration() methods
+    2015-03-25 - changed scheduler execute() method for a more precise delay calculation:
+                 1. Do not delay if any of the tasks ran (making request for immediate execution redundant)
+                 2. Delay is invoked only if none of the tasks ran
+                 3. Delay is based on the min anticipated wait until next task _AND_ the runtime of execute method itself.
+    2015-05-11 - added  restart() and restartDelayed() methods to restart tasks which are on hold after running all iterations
+    2015-05-19 - completely removed  delay from the scheduler since there are no power saving there. using 1 ms sleep instead
+
+v1.4.1:
+    2015-09-15 - more careful placement of AVR-specific includes for sleep method (compatibility with DUE)
+                 sleep on idle run is no longer a default and should be explicitly compiled with
+                _TASK_SLEEP_ON_IDLE_RUN defined
+
+v1.5.0:
+    2015-09-20 - access to currently executing task (for callback methods)
+    2015-09-20 - pass scheduler as a parameter to the task constructor to append the task to the end of the chain
+    2015-09-20 - option to create a task already enabled
+
+v1.5.1:
+    2015-09-21 - bug fix: incorrect handling of active tasks via set() and setIterations().
+                 Thanks to Hannes Morgenstern for catching this one
+
+v1.6.0:
+    2015-09-22 - revert back to having all tasks disable on last iteration.
+    2015-09-22 - deprecated disableOnLastIteration method as a result
+    2015-09-22 - created a separate branch 'disable-on-last-iteration' for this
+    2015-10-01 - made version numbers semver compliant (documentation only)
+
+v1.7.0:
+   2015-10-08 - introduced callback run counter - callback methods can branch on the iteration number.
+   2015-10-11 - enableIfNot() - enable a task only if it is not already enabled. Returns true if was already enabled,
+                false if was disabled.
+   2015-10-11 - disable() returns previous enable state (true if was enabled, false if was already disabled)
+   2015-10-11 - introduced callback methods "on enable" and "on disable". On enable runs every time enable is called,
+                on disable runs only if task was enabled
+   2015-10-12 - new Task method: forceNextIteration() - makes next iteration happen immediately during the next pass
+                regardless how much time is left
+
+v1.8.0:
+   2015-10-13 - support for status request objects allowing tasks waiting on requests
+   2015-10-13 - moved to a single header file to allow compilation control via #defines from the main sketch
+
+v1.8.1:
+   2015-10-22 - implement Task id and control points to support identification of failure points for watchdog timer logging
+
+v1.8.2:
+   2015-10-27 - implement Local Task Storage Pointer (allow use of same callback code for different tasks)
+   2015-10-27 - bug: currentTask() method returns incorrect Task reference if called within OnEnable and OnDisable methods
+   2015-10-27 - protection against infinite loop in OnEnable (if enable() methods are called within OnEnable)
+   2015-10-29 - new currentLts() method in the scheduler class returns current task's LTS pointer in one call
+
+v1.8.3:
+   2015-11-05 - support for task activation on a status request with arbitrary interval and number of iterations
+               (0 and 1 are still default values)
+   2015-11-05 - implement waitForDelayed() method to allow task activation on the status request completion
+                delayed for one current interval
+   2015-11-09 - added callback methods prototypes to all examples for Arduino IDE 1.6.6 compatibility
+   2015-11-14 - added several constants to be used as task parameters for readability (e.g, TASK_FOREVER, TASK_SECOND, etc.)
+   2015-11-14 - significant optimization of the scheduler's execute loop, including millis() rollover fix option
+
+v1.8.4:
+   2015-11-15 - bug fix: Task alignment with millis() for scheduling purposes should be done after OnEnable, not before.
+                Especially since OnEnable method can change the interval
+   2015-11-16 - further optimizations of the task scheduler execute loop
+
+v1.8.5:
+   2015-11-23 - bug fix: incorrect calculation of next task invocation in case callback changed the interval
+   2015-11-23 - bug fix: Task::set() method calls setInterval() explicitly, therefore delaying the task in the same manner
+
+v1.9.0:
+   2015-11-24 - packed three byte-long status variables into bit array structure data type - saving 2 bytes per each task instance
+
+v1.9.2:
+   2015-11-28 - _TASK_ROLLOVER_FIX is deprecated (not necessary)
+   2015-12-16 - bug fixes: automatic millis rollover support for delay methods
+   2015-12-17 - new method for _TASK_TIMECRITICAL option: getStartDelay()
+
+v2.0.0:
+   2015-12-22 - _TASK_PRIORITY - support for layered task prioritization
+
+v2.0.1:
+   2016-01-02 - bug fix: issue#11 Xtensa compiler (esp8266): Declaration of constructor does not match implementation
+
+v2.0.2:
+   2016-01-05 - bug fix: time constants wrapped inside compile option
+   2016-01-05 - support for ESP8266 wifi power saving mode for _TASK_SLEEP_ON_IDLE_RUN compile option
+
+v2.1.0:
+   2016-02-01 - support for microsecond resolution
+   2016-02-02 - added Scheduler baseline start time reset method: startNow()
+
+v2.2.0:
+   2016-11-17 - all methods made 'inline' to support inclusion of TaskSchedule.h file into other header files
+
+v2.2.1:
+   2016-11-30 - inlined constructors. Added "yield()" and "yieldOnce()" functions to easily break down and chain
+                back together long running callback methods
+   2016-12-16 - added "getCount()" to StatusRequest objects, made every task StatusRequest enabled.
+                Internal StatusRequest objects are accessible via "getInternalStatusRequest()" method.
+
+v2.3.0:
+   2017-02-24 - new timeUntilNextIteration() method within Scheduler class - inquire when a particlar task is
+                scheduled to run next time
+
+v2.4.0:
+   2017-04-27 - added destructor to the Task class to ensure tasks are disables and taken off the execution chain
+                upon destruction. (Contributed by Edwin van Leeuwen [BlackEdder - https://github.com/BlackEdder)
+
+v2.5.0:
+   2017-04-27 - ESP8266 ONLY: added optional support for std::functions via _TASK_STD_FUNCTION compilation option
+                (Contributed by Edwin van Leeuwen [BlackEdder - https://github.com/BlackEdder)
+   2017-08-30 - add _TASK_DEBUG making all methods and variables public FOR DEBUGGING PURPOSES ONLY!
+                Use at your own risk!
+   2017-08-30 - bug fix: Scheduler::addTask() checks if task is already part of an execution chain (github issue #37)
+   2017-08-30 - support for multi-tab sketches (Contributed by Adam Ryczkowski - https://github.com/adamryczkowski)
+
+v2.5.1:
+   2018-01-06 - support for IDLE sleep on Teensy boards (tested on Teensy 3.5)
+
+v2.5.2:
+   2018-01-09 - _TASK_INLINE compilation directive making all methods declared "inline" (issue #42)
+
+v2.6.0:
+   2018-01-30 - _TASK_TIMEOUT compilation directive: Task overall timeout functionality
+   2018-01-30 - ESP32 support (experimental)
+                (Contributed by Marco Tombesi: https://github.com/baggior)
+
+v2.6.1:
+   2018-02-13 - Bug: support for task self-destruction in the OnDisable method
+                Example 19: dynamic tasks creation and destruction
+   2018-03-14 - Bug: high level scheduler ignored if lower level chain is empty
+                Example 20: use of local task storage to work with task-specific class objects
+
+v3.0.0:
+   2018-03-15 - Major Release: Support for dynamic callback methods binding via compilation parameter _TASK_OO_CALLBACKS
+
+v3.0.1:
+   2018-11-09 - bug: task deleted from the execution chain cannot be added back (github issue #67)
+
+v3.0.2:
+   2018-11-11 - bug: default constructor is ambiguous when Status Request objects are enabled (github issue #65 & #68)
+
+v3.0.3:
+   2019-06-13 - feature: custom sleep callback method: setSleepMethod() - ability to dynamically control idle sleep for various microcontrollers
+              - feature: support for MSP430 and MSP432 boards (pull request #75: big thanks to Guillaume Pirou, https://github.com/elominp)
+              - officially discontinued support for offile documentation in favor of updating the Wiki pages
+
+v3.1.0:
+   2020-01-07 - feature: added 4 cpu load monitoring methods for _TASK_TIMECRITICAL compilation option
+
+v3.1.1:
+   2020-01-09 - update: more precise CPU load measuring. Ability to define idle sleep threshold for ESP chips
+
+v3.1.2:
+   2020-01-17 - bug fix: corrected external forward definitions of millis() and micros
+
+v3.1.3:
+   2020-01-30 - bug fix: _TASK_DEFINE_MILLIS to force forward definition of millis and micros. Not defined by default. 
+   2020-02-16 - bug fix: add 'virtual' to the Task destructor definition (issue #86)
+
+v3.1.4:
+   2020-02-22 - bug: get rid of unnecessary compiler warnings
+   2020-02-22 - feature: access to the task chain with _TASK_EXPOSE_CHAIN compile option
+
+v3.1.5:
+   2020-05-08 - feature: implemented light sleep for esp32
+
+v3.1.6:
+   2020-05-12 - bug fix: deleteTask and addTask should check task ownership first (Issue #97)
+
+v3.1.7:
+   2020-07-07 - warning fix: unused parameter 'aRecursive' (Issue #99)
+
+v3.2.0:
+   2020-08-16 - feature: scheduling options
+
+v3.2.1:
+   2020-10-04 - feature: Task.abort method. Stop task execution without calling OnDisable(). 
+
+v3.2.2:
+   2020-12-14 - feature: enable and restart methods return true if task enabled 
+                feature: Task.cancel() method - disable task with a cancel flag (could be used for alt. path
+                         processing in the onDisable method.
+                feature: Task.cancelled() method - indicates that task was disabled with a cancel() method.
+
+v3.2.3:
+   2021-01-01 - feature: discontinued use of 'register' keyword. Depricated in C++ 11 
+                feature: add STM32 as a platform supporting _TASK_STD_FUNCTION. (PR #105)
 
+v3.3.0:
+   2021-05-11 - feature: Timeout() methods for StatusRequest objects 
+
+v3.4.0:
+   2021-07-14 - feature: ability to Enable/Disable and Pause/Resume scheduling 
+              - feature: optional use of external millis/micros methods 
+
+v3.5.0:
+   2021-11-01 - feature: adjust(long aInterval) method - adjust execution schedule: 
+                + aInterval - shift schedule forward (later)
+                - aInterval - shift schedule backwards (earlier)
+
+v3.6.0:
+   2021-11-01 - feature: _TASK_THREAD_SAFE compile option for multi-core systems or running under RTOS 
+
+
+*/
 
 
 #include <Arduino.h>
@@ -243,6 +247,7 @@ extern "C" {
 // #define _TASK_SCHEDULING_OPTIONS // Support for multiple scheduling options
 // #define _TASK_DEFINE_MILLIS      // Force forward declaration of millis() and micros() "C" style
 // #define _TASK_EXTERNAL_TIME      // Custom millis() and micros() methods
+// #define _TASK_THREAD_SAFE        // Enable additional checking for thread safety
 
  #ifdef _TASK_MICRO_RES
 
@@ -391,24 +396,48 @@ void StatusRequest::signalComplete(int aStatus) {
  *  If aStatusRequest is NULL, request for waiting is ignored, and the waiting task is not enabled.
  */
 bool Task::waitFor(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) {
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     iStatusRequest = aStatusRequest;
     if ( iStatusRequest != NULL ) { // assign internal StatusRequest var and check if it is not NULL
         setIterations(aIterations);
         setInterval(aInterval);
         iStatus.waiting = _TASK_SR_NODELAY;  // no delay
+        
+#ifdef _TASK_THREAD_SAFE
+        iMutex--;
+#endif  // _TASK_THREAD_SAFE
+        
         return enable();
     }
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
+
     return false;
 }
 
 bool Task::waitForDelayed(StatusRequest* aStatusRequest, unsigned long aInterval, long aIterations) {
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     iStatusRequest = aStatusRequest;
     if ( iStatusRequest != NULL ) { // assign internal StatusRequest var and check if it is not NULL
         setIterations(aIterations);
         if ( aInterval ) setInterval(aInterval);  // For the dealyed version only set the interval if it was not a zero
         iStatus.waiting = _TASK_SR_DELAY;  // with delay equal to the current interval
+#ifdef _TASK_THREAD_SAFE
+        iMutex--;
+#endif  // _TASK_THREAD_SAFE
         return enable();
     }
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
+
     return false;
 }
 
@@ -454,6 +483,11 @@ void Task::setOnDisable(TaskOnDisable aCallback) { iOnDisable = aCallback; }
  * out of the execution chain as a result
  */
 void Task::reset() {
+  
+#ifdef _TASK_THREAD_SAFE
+    iMutex = 1;
+#endif  // _TASK_THREAD_SAFE
+
     iStatus.enabled = false;
     iStatus.inonenable = false;
     iStatus.canceled = false;
@@ -492,6 +526,10 @@ void Task::reset() {
     iStarttime = 0;
     iStatus.timeout = false;
 #endif  // _TASK_TIMEOUT
+
+#ifdef _TASK_THREAD_SAFE
+    iMutex = 0;
+#endif  // _TASK_THREAD_SAFE
 }
 
 /** Explicitly set Task execution parameters
@@ -506,13 +544,30 @@ void Task::reset() {
 void Task::set(unsigned long aInterval, long aIterations) {
 #else
 void Task::set(unsigned long aInterval, long aIterations, TaskCallback aCallback, TaskOnEnable aOnEnable, TaskOnDisable aOnDisable) {
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     iCallback = aCallback;
     iOnEnable = aOnEnable;
     iOnDisable = aOnDisable;
+
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
+
 #endif // _TASK_OO_CALLBACKS
 
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     setInterval(aInterval);
     iSetIterations = iIterations = aIterations;
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
+
 }
 
 /** Sets number of iterations for the task
@@ -520,7 +575,15 @@ void Task::set(unsigned long aInterval, long aIterations, TaskCallback aCallback
  * @param aIterations - number of iterations, use -1 for no limit
  */
 void Task::setIterations(long aIterations) {
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     iSetIterations = iIterations = aIterations;
+
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
 }
 
 #ifndef _TASK_OO_CALLBACKS
@@ -529,6 +592,10 @@ void Task::setIterations(long aIterations) {
  * @param aCallback - pointer to the callback method for the next step
  */
 void Task::yield (TaskCallback aCallback) {
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     iCallback = aCallback;
     forceNextIteration();
 
@@ -537,14 +604,26 @@ void Task::yield (TaskCallback aCallback) {
     // a series of callback methods
     iRunCounter--;
     if ( iIterations >= 0 ) iIterations++;
+
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
 }
 
 /** Prepare task for next step iteration following yielding of control to the scheduler
  * @param aCallback - pointer to the callback method for the next step
  */
 void Task::yieldOnce (TaskCallback aCallback) {
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     yield(aCallback);
     iIterations = 1;
+
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
 }
 #endif // _TASK_OO_CALLBACKS
 
@@ -555,6 +634,11 @@ void Task::yieldOnce (TaskCallback aCallback) {
  */
 bool Task::enable() {
     if (iScheduler) { // activation without active scheduler does not make sense
+
+#ifdef _TASK_THREAD_SAFE
+        iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
         iRunCounter = 0;
         iStatus.canceled = false;
 
@@ -592,6 +676,11 @@ bool Task::enable() {
             iMyStatusRequest.setWaiting();
 #endif // _TASK_STATUS_REQUEST
         }
+
+#ifdef _TASK_THREAD_SAFE
+        iMutex--;
+#endif  // _TASK_THREAD_SAFE
+
         return iStatus.enabled;
     }
     return false;
@@ -601,8 +690,17 @@ bool Task::enable() {
  * Returns previous state (true if was already enabled, false if was not)
  */
 bool Task::enableIfNot() {
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     bool previousEnabled = iStatus.enabled;
     if ( !previousEnabled ) enable();
+
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
+
     return (previousEnabled);
 }
 
@@ -610,20 +708,45 @@ bool Task::enableIfNot() {
  * and schedules it for execution after a delay = aInterval
  */
 bool Task::enableDelayed(unsigned long aDelay) {
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     enable();
     delay(aDelay);
+
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
+
     return iStatus.enabled;
 }
 
 #ifdef _TASK_TIMEOUT
 void Task::setTimeout(unsigned long aTimeout, bool aReset) {
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     iTimeout = aTimeout;
     if (aReset) resetTimeout();
+
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
 }
 
 void Task::resetTimeout() {
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     iStarttime = _TASK_TIME_FUNCTION();
     iStatus.timeout = false;
+
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
 }
 
 unsigned long Task::getTimeout() {
@@ -650,14 +773,27 @@ bool Task::timedOut() {
  * if aDelay is zero, delays for the original scheduling interval from now
  */
 void Task::delay(unsigned long aDelay) {
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     iDelay = aDelay ? aDelay : iInterval;
     iPreviousMillis = _TASK_TIME_FUNCTION(); 
+
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
 }
 
 /** Adjusts Task execution with aInterval (if task is enabled).
  */
 void Task::adjust(long aInterval) {
     if ( aInterval == 0 ) return;  //  nothing to do for a zero
+
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     if ( aInterval < 0 ) {
       iPreviousMillis += aInterval;
     }
@@ -665,6 +801,9 @@ void Task::adjust(long aInterval) {
       iDelay += aInterval;  //  we have to adjust delay because adjusting iPreviousMillis might push
                             //  it into the future beyond current millis() and cause premature trigger
     }
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
 }
 
 
@@ -673,7 +812,15 @@ void Task::adjust(long aInterval) {
  * Task's original schedule is shifted, and all subsequent iterations will continue from this point in time
  */
 void Task::forceNextIteration() {
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     iPreviousMillis = _TASK_TIME_FUNCTION() - (iDelay = iInterval);
+
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
 }
 
 /** Sets the execution interval.
@@ -682,8 +829,16 @@ void Task::forceNextIteration() {
  * @param aInterval - new execution interval
  */
 void Task::setInterval (unsigned long aInterval) {
+#ifdef _TASK_THREAD_SAFE
+    iMutex++;
+#endif  // _TASK_THREAD_SAFE
+
     iInterval = aInterval;
     delay(); // iDelay will be updated by the delay() function
+
+#ifdef _TASK_THREAD_SAFE
+    iMutex--;
+#endif  // _TASK_THREAD_SAFE
 }
 
 /** Disables task
@@ -807,12 +962,13 @@ Scheduler::~Scheduler() {
 /** Initializes all internal varaibles
  */
 void Scheduler::init() {
+    iEnabled = false;
+    
     iFirst = NULL;
     iLast = NULL;
     iCurrent = NULL;
 
     iPaused = false;
-    iEnabled = true;  
 
 #ifdef _TASK_PRIORITY
     iHighPriority = NULL;
@@ -825,6 +981,8 @@ void Scheduler::init() {
 #ifdef _TASK_TIMECRITICAL
     cpuLoadReset();
 #endif  // _TASK_TIMECRITICAL
+
+    iEnabled = true;  
 }
 
 /** Appends task aTask to the tail of the execution chain.
@@ -832,12 +990,13 @@ void Scheduler::init() {
  * @note Task can only be part of the chain once.
  */
  void Scheduler::addTask(Task& aTask) {
-
 // If task already belongs to a scheduler, we should not be adding
 // it to this scheduler. It should be deleted from the other scheduler first. 
     if (aTask.iScheduler != NULL)
         return;
 
+    iEnabled = false;
+
     aTask.iScheduler = this;
 // First task situation:
     if (iFirst == NULL) {
@@ -852,6 +1011,8 @@ void Scheduler::init() {
 // "Previous" last task gets linked to this one - as this one becomes the last one
     aTask.iNext = NULL;
     iLast = &aTask;
+
+    iEnabled = true;
 }
 
 /** Deletes specific Task from the execution chain
@@ -862,17 +1023,21 @@ void Scheduler::deleteTask(Task& aTask) {
     if (aTask.iScheduler != this) 
         return;
     
+    iEnabled = false;
+
     aTask.iScheduler = NULL;
     if (aTask.iPrev == NULL) {
         if (aTask.iNext == NULL) {
             iFirst = NULL;
             iLast = NULL;
+            iEnabled = true;
             return;
         }
         else {
             aTask.iNext->iPrev = NULL;
             iFirst = aTask.iNext;
             aTask.iNext = NULL;
+            iEnabled = true;
             return;
         }
     }
@@ -881,6 +1046,7 @@ void Scheduler::deleteTask(Task& aTask) {
         aTask.iPrev->iNext = NULL;
         iLast = aTask.iPrev;
         aTask.iPrev = NULL;
+        iEnabled = true;
         return;
     }
 
@@ -888,6 +1054,8 @@ void Scheduler::deleteTask(Task& aTask) {
     aTask.iNext->iPrev = aTask.iPrev;
     aTask.iPrev = NULL;
     aTask.iNext = NULL;
+    
+    iEnabled = true;
 }
 
 /** Disables all tasks in the execution chain
@@ -900,6 +1068,9 @@ void Scheduler::disableAll(bool aRecursive) {
 #else
 void Scheduler::disableAll() {
 #endif
+
+    iEnabled = false;
+    
     Task    *current = iFirst;
     while (current) {
         current->disable();
@@ -909,6 +1080,8 @@ void Scheduler::disableAll() {
 #ifdef _TASK_PRIORITY
     if (aRecursive && iHighPriority) iHighPriority->disableAll(true);
 #endif  // _TASK_PRIORITY
+
+    iEnabled = true;
 }
 
 
@@ -920,6 +1093,9 @@ void Scheduler::enableAll(bool aRecursive) {
 #else
 void Scheduler::enableAll() {
 #endif    
+
+    iEnabled = false;
+    
     Task    *current = iFirst;
     while (current) {
         current->enable();
@@ -930,6 +1106,7 @@ void Scheduler::enableAll() {
     if (aRecursive && iHighPriority) iHighPriority->enableAll(true);
 #endif  // _TASK_PRIORITY
 
+    iEnabled = true;
 }
 
 /** Sets scheduler for the higher priority tasks (support for layered task priority)
@@ -963,6 +1140,8 @@ void Scheduler::startNow() {
 #endif
     unsigned long t = _TASK_TIME_FUNCTION();
 
+    iEnabled = false;
+    
     iCurrent = iFirst;
     while (iCurrent) {
         if ( iCurrent->iStatus.enabled ) iCurrent->iPreviousMillis = t - iCurrent->iDelay;
@@ -972,6 +1151,8 @@ void Scheduler::startNow() {
 #ifdef _TASK_PRIORITY
     if (aRecursive && iHighPriority) iHighPriority->startNow( true );
 #endif  // _TASK_PRIORITY
+
+    iEnabled = true;
 }
 
 /** Returns number millis or micros until next scheduled iteration of a given task
@@ -1067,7 +1248,7 @@ bool Scheduler::execute() {
         iCurrentScheduler = this;
 #endif  // _TASK_PRIORITY
 
-    //  each scheduled is enabled/disabled individually, so check iEnabed only
+    //  each scheduled is enabled/disabled individually, so check iEnabled only
     //  after the higher priority scheduler has been invoked.
     if ( !iEnabled ) return true; //  consider this to be an idle run
 
@@ -1087,6 +1268,12 @@ bool Scheduler::execute() {
         do {
             if ( iCurrent->iStatus.enabled ) {
 
+#ifdef _TASK_THREAD_SAFE
+            //  this task is in the scheduling state and should not be invoked
+            //  as there could be incosistent settings until scheduling is done
+            if ( iCurrent->iMutex ) break;
+#endif  // _TASK_THREAD_SAFE
+
 #ifdef _TASK_WDT_IDS
     // For each task the control points are initialized to avoid confusion because of carry-over:
                 iCurrent->iControlPoint = 0;

+ 15 - 10
src/TaskSchedulerDeclarations.h

@@ -27,6 +27,7 @@
 // #define _TASK_SCHEDULING_OPTIONS // Support for multiple scheduling options
 // #define _TASK_DEFINE_MILLIS      // Force forward declaration of millis() and micros() "C" style
 // #define _TASK_EXTERNAL_TIME      // Custom millis() and micros() methods
+// #define _TASK_THREAD_SAFE        // Enable additional checking for thread safety
 
 class Scheduler;
 
@@ -141,17 +142,16 @@ typedef bool (*TaskOnEnable)();
 #endif  // _TASK_SLEEP_ON_IDLE_RUN
 
 typedef struct  {
-    bool  enabled    : 1;           // indicates that task is enabled or not.
-    bool  inonenable : 1;           // indicates that task execution is inside OnEnable method (preventing infinite loops)
-    bool  canceled   : 1;           // indication that tast has been canceled prior to normal end of all iterations or regular call to disable()
+    bool  enabled       : 1;           // indicates that task is enabled or not.
+    bool  inonenable    : 1;           // indicates that task execution is inside OnEnable method (preventing infinite loops)
+    bool  canceled      : 1;           // indication that tast has been canceled prior to normal end of all iterations or regular call to disable()
 #ifdef _TASK_STATUS_REQUEST
-    uint8_t  waiting : 2;           // indication if task is waiting on the status request
+    uint8_t  waiting    : 2;           // indication if task is waiting on the status request
 #endif
 
 #ifdef _TASK_TIMEOUT
-    bool  timeout    : 1;           // indication if task timed out
+    bool  timeout       : 1;           // indication if task timed out
 #endif
-
 } __task_status;
 
 
@@ -310,6 +310,11 @@ class Task {
     unsigned long            iTimeout;               // Task overall timeout
     unsigned long            iStarttime;             // millis at task start time
 #endif // _TASK_TIMEOUT
+
+
+#ifdef _TASK_THREAD_SAFE
+    volatile uint8_t          iMutex;                // a mutex to pause scheduling during chages to the task
+#endif
 };
 
 class Scheduler {
@@ -366,15 +371,15 @@ class Scheduler {
 #endif // _TASK_EXPOSE_CHAIN
 
   _TASK_SCOPE:
-    Task       *iFirst, *iLast, *iCurrent;        // pointers to first, last and current tasks in the chain
+    Task          *iFirst, *iLast, *iCurrent;        // pointers to first, last and current tasks in the chain
 
-    bool       iPaused, iEnabled;
+    volatile bool iPaused, iEnabled;
 #ifdef _TASK_SLEEP_ON_IDLE_RUN
-    bool        iAllowSleep;                      // indication if putting MC to IDLE_SLEEP mode is allowed by the program at this time.
+    bool          iAllowSleep;                      // indication if putting MC to IDLE_SLEEP mode is allowed by the program at this time.
 #endif  // _TASK_SLEEP_ON_IDLE_RUN
 
 #ifdef _TASK_PRIORITY
-    Scheduler  *iHighPriority;                    // Pointer to a higher priority scheduler
+    Scheduler*    iHighPriority;                    // Pointer to a higher priority scheduler
 #endif  // _TASK_PRIORITY
 
 #ifdef _TASK_TIMECRITICAL