jamesmh / coravel

Near-zero config .NET library that makes advanced application features like Task Scheduling, Caching, Queuing, Event Broadcasting, and more a breeze!

Home Page:https://docs.coravel.net/Installation/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Task, scheduled to run on the same second, are not run in order they were added.

arvinboggs opened this issue · comments

Describe the bug
Task are not run in order they were added (via ScheduleAsync) even though they have the same schedule, parameter, etc. and they are scheduled to run on the same second.

Affected Coravel Feature
Scheduling

Expected behaviour
If multiple tasks were to be run at the same time, they should be run in order in which they were added to the scheduler.

code snippet

static int mCounter;
public string AddSchedule()
{
    mCounter++;
    var pCounter = mCounter;
    // Program.Scheduler is a IScheduler 
    Program.Scheduler.ScheduleAsync(async () =>
    {
        Console.WriteLine(DateTime.Now + " running task started by AddSchedule. pCounter = " + pCounter);
        await Task.Delay(10000);
        Console.WriteLine(DateTime.Now + " done running task started by AddSchedule. pCounter = " + pCounter);
    })
        .EverySecond()
        .RunOnceAtStart()
        .PreventOverlapping("unique key for this group of tasks")
        .Once();
    return DateTime.Now + " AddSchedule. pCounter = " + pCounter; 
}

Now, call it 5 times:

for (var p = 0; p <= 5 - 1; p++)
{
    AddSchedule();
}

actual result
6/13/2024 12:05:33 PM running task started by AddSchedule. pCounter = 4
6/13/2024 12:05:43 PM done running task started by AddSchedule. pCounter = 4
6/13/2024 12:05:43 PM running task started by AddSchedule. pCounter = 5
6/13/2024 12:05:53 PM done running task started by AddSchedule. pCounter = 5
6/13/2024 12:05:53 PM running task started by AddSchedule. pCounter = 1
6/13/2024 12:06:03 PM done running task started by AddSchedule. pCounter = 1
6/13/2024 12:06:03 PM running task started by AddSchedule. pCounter = 3
6/13/2024 12:06:13 PM done running task started by AddSchedule. pCounter = 3
6/13/2024 12:06:13 PM running task started by AddSchedule. pCounter = 2
6/13/2024 12:06:23 PM done running task started by AddSchedule. pCounter = 2

expected result
6/13/2024 12:05:33 PM running task started by AddSchedule. pCounter = 1
6/13/2024 12:05:43 PM done running task started by AddSchedule. pCounter = 1
6/13/2024 12:05:43 PM running task started by AddSchedule. pCounter = 2
6/13/2024 12:05:53 PM done running task started by AddSchedule. pCounter = 2
6/13/2024 12:05:53 PM running task started by AddSchedule. pCounter = 3
6/13/2024 12:06:03 PM done running task started by AddSchedule. pCounter = 3
6/13/2024 12:06:03 PM running task started by AddSchedule. pCounter = 4
6/13/2024 12:06:13 PM done running task started by AddSchedule. pCounter = 4
6/13/2024 12:06:13 PM running task started by AddSchedule. pCounter = 5
6/13/2024 12:06:23 PM done running task started by AddSchedule. pCounter = 5

This isn't a bug, it's expected behavior. The main reason why we don't restrict ordering is mostly so that we don't limit the throughput of scheduled tasks that are due. If each task had to wait until another one was done then the throughput would be horrendous.

This is not because we execute the tasks in a random order - but because of async/await. The thread/synchronization scheduler (in .NET) is responsible for handling which task will be given the thread after their IO / await is completed. Again, for reasons of throughput and how thread scheduling works to enable that, tasks will be run in order up until the first call to await, and then the thread scheduler will determine which task will be given the worker thread first (which is probably not in the order they started).

This is the first time I've seen this expectation, so I would say the majority of those using Coravel don't expect separate tasks to be linked in any way - including temporally.

What you are describing is more like the behavior of a workflow. This can be achieved in some other ways:

  • 1 scheduled task that in serial calls/executes the required steps
  • Use event broadcasting where each listener then emits the "next" event to execute in the workflow (see documentation here)

Hopefully that helps!