TcMenu / TaskManagerIO

A task, event, scheduling, and interrupt marshalling library for Arduino and mbed boards.
Apache License 2.0
122 stars 12 forks source link

Ability to reschedule a task #35

Closed AlirezaSalehy closed 2 years ago

AlirezaSalehy commented 2 years ago

Hi there! First thanks for this great library.

I think that there is no possibility to reschedule a task in latest version of library right? So i am asking whether there is a solution for this or would you please add it in next version?

This is specially useful when a task has a default service rate and later in execution we adjust service rate, base on run-time conditions.

Kind regards. A. Salehy

davetcc commented 2 years ago

You cannot change the schedule of a task that was started at a fixed rate, this is because the library is used within RTOS environments and it's difficult to make that thread safe without locking. However, there are two other solutions:

Keep rescheduling a single shot task (aka Javascript's setTimeout), for example:

void myTask() {
    // do some work
    taskManager.scheduleOnce(myIntervalInMillis, myTask);
}

// then within setup, loop or main initiate by calling it for the first time
myTask();

The other way is to use an event, where you can both control the next execution time, and also trigger it at any time. See https://www.thecoderscorner.com/products/arduino-libraries/taskmanager-io/using-polled-events-taskmanager/

AlirezaSalehy commented 2 years ago

Thanks very much for instant reply. Nice alternative way. I tried this way though before reading your comment. I used a while loop and a class property that can be changed from somewhere outside task and taksmanager.yeildForMicros function as bellow:

  class HeartBeatExecuatble : public Executable {
      public:
          Link* link;
          HeartBeatExecuatble(Link* link) {
              this->link = link;
          }

          void exec() override {
              while (1) {
                  this->link->sendHeartBeat();
                  taskManager.yieldForMicros(this->link->heartbeatTickMS * 1000);
              }
          }    

          ~HeartBeatExecuatble() override {};
  };

Do you think it's a correct approach or should be replaced by your recommended ways? I mean is there chance of stack overflow due to increasing number of function stack frames filling up stack? Because i think if i use yeildForMicros in all of my tasks then none of them will clean up their stack frames. Am i wrong? because i think that's how it should work.

Thanks in advance!

davetcc commented 2 years ago

I would use the way I showed, yielding has a side effect of building up the call stack.

AlirezaSalehy commented 2 years ago

So this means that there is an ever increase in building up call stack? Then if we keep using while loop with yield within, for all tasks then stack will eventually overflow sooner of later?

Thanks so i try to implement the way you demonstrated.

AlirezaSalehy commented 2 years ago

I would use the way I showed, yielding has a side effect of building up the call stack.

Dear Dave can you please respond to my above question?

Thanks!

davetcc commented 2 years ago

Every time you call yield, you essentially nest the runloop within your code, it's designed for small delays in microseconds while tasks happen. Even in the worst case, let's say to had 20 tasks, and each yielded, you'd be 20 deep which on most boards would be fine.

An example usage of yieldForMicros would be waiting for some hardware state to settle.

For example this is how it would look in code if you had three tasks and 2 of them called yield:

   taskManager.runLoop()
    task1.execute
         task1 calls yieldForMicros
             evaluate run loop (task1 cannot be scheduled as it is running)
              task2.execute
                   task2 calls yieldForMicros
                   evaluate runloop (task1 and task2 cannot be scheduled as they are running)
                   task3.execute is called
                   task3 finishes
                   yieldForMiros of task2 exits.
             task2 finishes
             yieldForMicros of task1 exits.
       task1 finishes
    next runLoop   

In future please can you ask questions in the forum (https://www.thecoderscorner.com/jforum/recentTopics/list.page). There is however no guarantee of an answer there either. I try to answer on a best efforts basis. It keeps the noise down here. Please close this issue once read and understood

AlirezaSalehy commented 2 years ago

In future please can you ask questions in the forum (https://www.thecoderscorner.com/jforum/recentTopics/list.page). There is however no guarantee of an answer there either. I try to answer on a best efforts basis. It keeps the noise down here. Please close this issue once read and understood

Alright thanks, for future issues i'll post there.

I got your example, But what if we have all tasks doing there services as the model bellow:

task_n:
while (1) {
    do_service_n();
    taskmanager.yieldForMicros(<SOME_MICROS>);
}

Then call stack keeps growing up until it overflows?

davetcc commented 2 years ago

No, within yieldForMicros the only possibility of increasing the stack is if another yieldForMicros is located within another task. While within a yield for micros the calling task is frozen and will not be called recursively.

runLoop
    execute taskN
        yieldForMicros
           while(duration not reached)
               execute additional tasks (but do not reenter taskN)
           end while
       finish yield
    finish taskN
davetcc commented 2 years ago

Just for anyone else looking at this issue, I don't recommend using task manager the way presented here with busy loops. Yield for micros is not intended for use in busy loops, I don't recommend you use task manager if that's the way you're composing the application, it is not a good fit.

davetcc commented 2 years ago

Reason for that is that it is not a scheduler in the way you're thinking it is, it uses unfair semantics and with many busy loops you'll risk tasks not being run. It can execute on a schedule, immediately, on an interrupt or event taking place. But a task scheduler it is not.

AlirezaSalehy commented 2 years ago

Thanks Dave.

I wish you the best.

davetcc commented 2 years ago

I hope you didn't think my last comment to be rude, but I'd rather be upfront that we've never tested that scenario and have doubts about it working properly. Rather than bake it into your project and find problems later.

The intended purpose of yieldForMicros(..) is for waits longer than a few microseconds to make code look more synchronous and readable. Here's an example:

void onTakeReadingFromHardware() {
     myHardware.initialise(); // takes 500uS to start..
     taskManager.yieldForMicros(500); // allows other tasks to run
     myHardware.doSomething(); // now we do something with the hardware
}
davetcc commented 2 years ago

At the end of the day, you could test it in a lot of scenarios and see if it does work for your cases. It may well work but we just don't know as we've never considered using it that way. If you did, I'd focus on:

  1. Do all the busy loops continue to function over a time period of hours.
  2. What happens to other tasks written the traditional way, do they schedule.
  3. How deep is the call stack (you may need a hardware debugger to determine this).
AlirezaSalehy commented 2 years ago

I hope you didn't think my last comment to be rude, but I'd rather be upfront that we've never tested that scenario and have doubts about it working properly. Rather than bake it into your project and find problems later.

Yes i didn't think so. You answered in a good manner, and i appreciate. I wish i could donate to support but i'm not able to involve in international payments due to beast dictator which literally ramming his evil dreams down our throat while holding us beaten and tied by ruining our dream DEMOCRACY.

It may well work but we just don't know as we've never considered using it that way. If you did, I'd focus on:

  1. Do all the busy loops continue to function over a time period of hours.
  2. What happens to other tasks written the traditional way, do they schedule.
  3. How deep is the call stack (you may need a hardware debugger to determine this).

Yes i can do this. I'll email you the results as soon as i am free to do the experiment.