Jump to content

Best way to do a scheduled task queue


Go to solution Solved by Acanis,

Recommended Posts

Posted

Hey,

what is the best way to do a scheduled task queue?

Iam usally using OnEvent in my GUI and if I have to time tasks, I use AdlibRegister and AdlibUnRegister. Thats working pretty well: one task doesnt disturb the other ones. 

But I cant see the status of the Adlibs and Im always happy to learn about better ways to do something :) I found the "_AdlibEnhance.au3", which should improve the possibilites, but its still Adlib.

A little bit more specific on the use case: 

Quote

A GUI with different parts that are to be updated from different data sources at certain intervals. There is also the option for the user to insert additional tasks (send an immediate update or reminder at a certain time or output an analysis from the available data, etc.) and to delete tasks. A task would be calling a function (in best case I can use parameters).
However, tasks should not interrupt each other and AutoIt does not want to do multithreading or anything like that.
And for debugging purposes, I would also like to be able to see which tasks are currently scheduled, in which order and how long it will take or approximately when it will happen.

I don't believe that this is a special use case and think that there must be a UDF for this that I just haven't found yet or that there are at least better solutions than "Adlib". 

 

Maybe some "sorted list" (by timestamps) and checking the top entry timestamp every few ms in the main loop? And If I add a new entry, I sort the list again? And it just checks, if the tasks timestamp is in the past?! And to add entries, for example "check the weather every 5 min", I just calculate the target time stamp and I use the "check weather function" to add another entry, after the check is done. 

Not sure, how I call the functions from something like this. Maybe with a string of the function in the list and then using "Eval()"?! 

Posted

Not really sure what do you want to achieve but you can use a dummy control to process responses from your functions outside the functions body, even using blocking functions.

HotKeySet('{ESC}', 'Quit')

AdlibRegister('F1', 15000)
AdlibRegister('F2', 5000)

$hGUI = GUICreate('', 0, 0, 0, 0, 0x80000000)
$hDummy = GUICtrlCreateDummy()
GUISetState(@SW_SHOW)

While True
    Switch GUIGetMsg()
        Case $hDummy
            ; You can process here the responses from your functions by your will
            ConsoleWrite(GUICtrlRead($hDummy) & @CRLF)
    EndSwitch
WEnd

Func F1()
    GUICtrlSendToDummy($hDummy, 'I am F1: Here I can send some data...')
EndFunc

Func F2()
    GUICtrlSendToDummy($hDummy, 'I am F2: Here I can send some data...')
EndFunc

Func Quit()
    Exit
EndFunc

 

Posted (edited)

Maybe Iam too tired and my description wasnt good, sorry...^^
 

I would like to know, if something like this (maybe not with an array, but its quick'n'dirty to show, what I try to achieve):

#include <Array.au3>

Global $taskQueue[0][2] ; [0] = Timestamp, [1] = Function

AddTask(TimerInit() + 2500, "MyRepeatableFunction")
AddTask(TimerInit() + 15000, "MyFunction")
AddTask(TimerInit() + 20000, "MyFunction")
AddTask(TimerInit() + 20000, "_ExitFunction")

While Sleep(250)
    CheckTasks()
WEnd

Func MyFunction()
    ConsoleWrite("Task completed!" & @CRLF)
EndFunc

Func _ExitFunction()
    ConsoleWrite("Getting closed!" & @CRLF)
    Exit
EndFunc

Func MyRepeatableFunction()
    ConsoleWrite("Repeatable task completed!" & @CRLF)

    AddTask(TimerInit() + 2500, "MyRepeatableFunction") ; add the same function again
EndFunc

Func AddTask($interval, $function)
    Local $aTask[1][2] = [[$interval, $function]]

    _ArrayAdd($taskQueue, $aTask)
    _ArraySort($taskQueue)
EndFunc

Func CheckTasks()
    Local $currentTime = TimerInit()

    If UBound($taskQueue) > 0 Then
        If $currentTime >= $taskQueue[0][0] Then
            Call($taskQueue[0][1]) ; call the function
            _ArrayDelete($taskQueue, 0) ; delete the task
        EndIf
    EndIf
EndFunc

Its not working on the repeatable task and Iam not sure, if this direction is a "good" one... Like this I would have a global list with all the tasks and next timings.

The code is now without GUI, because it is basically the same and so it is easier to show what I mean by "scheduled task queue".

Edited by Acanis
Posted

I do not think AutoIt is well suited to perform a task scheduler application.  Since AutoIt is single thread, it would make it hard to follow the time sheet if multiple tasks are to be executed in parallel.  You would want to run/call each task on separate thread and keep main thread looking for the next job to be executed.

Posted (edited)

if it serves you?

#include <Array.au3>

; [0] = hTimer [1]Timestamp, [2] = Function
Global $taskQueue[5][3] = [ _
        [4, "Timestamp", "Function"], _
        [0, 3000, "Task1"], _
        [0, 5000, "Task2"], _
        [0, 12000, "Task3"], _
        [0, 60000, "_ExitFunction"]]

While Sleep(250)
    CheckTasks()
WEnd

;--------------------------------------------------------------------------------------------------------------------------------
Func _ExitFunction()
    ConsoleWrite("Getting closed!" & @CRLF)
    Exit
EndFunc   ;==>_ExitFunction
;--------------------------------------------------------------------------------------------------------------------------------
Func CheckTasks()
    For $i = 1 To $taskQueue[0][0]

        If $taskQueue[$i][0] = 0 Then
            $taskQueue[$i][0] = TimerInit()
            ContinueLoop
        EndIf

        If TimerDiff($taskQueue[$i][0]) > $taskQueue[$i][1] Then
            ConsoleWrite("call " & $taskQueue[$i][2] & @CRLF)
            ConsoleWrite(" time:" & Round(TimerDiff($taskQueue[$i][0]) / 1000, 3) & " seconds")
            
            Call($taskQueue[$i][2])
            $taskQueue[$i][0] = TimerInit()
        EndIf

    Next
EndFunc
;--------------------------------------------------------------------------------------------------------------------------------
Func Task1()
    ConsoleWrite("- Task1 completed!" & @CRLF)
EndFunc   ;==>MyFunction
;--------------------------------------------------------------------------------------------------------------------------------
Func Task2()
    ConsoleWrite("- Task2 completed!" & @CRLF)
EndFunc   ;==>MyFunction
;--------------------------------------------------------------------------------------------------------------------------------
Func Task3()
    ConsoleWrite("- Task3 completed!" & @CRLF)
EndFunc   ;==>MyFunction
;---------------------------------------------

 

Edit:
It is certainly better, as the above said, to put each task on a separate thread

#include <Array.au3>

; [0] = hTimer [1]Timestamp, [2] = Function, [3] = Parameter
Global $taskQueue[5][4] = [ _
        [4, "Timestamp", "Function", "Parameter"], _
        [0, 3000, "Task1.au3", ""], _
        [0, 5000, "Task2.au3", "-one -two"], _
        [0, 8000, "Task3.au3", '-this "and this"'], _
        [0, 60000, "Exit", ""]]

While Sleep(250)
    CheckTasks()
WEnd

;--------------------------------------------------------------------------------------------------------------------------------
Func _ExitFunction()
    ConsoleWrite("Getting closed!" & @CRLF)
    Exit
EndFunc   ;==>_ExitFunction
;--------------------------------------------------------------------------------------------------------------------------------
Func CheckTasks()
    For $i = 1 To $taskQueue[0][0]

        If $taskQueue[$i][0] = 0 Then
            $taskQueue[$i][0] = TimerInit()
            ContinueLoop
        EndIf

        If TimerDiff($taskQueue[$i][0]) > $taskQueue[$i][1] Then
            ConsoleWrite("call " & $taskQueue[$i][2] & @CRLF)
            ConsoleWrite(" time:" & Round(TimerDiff($taskQueue[$i][0]) / 1000, 3) & " seconds")

            If $taskQueue[$i][2] = "Exit" Then Exit

            RunTask(@ScriptDir & "\" & $taskQueue[$i][2], $taskQueue[$i][3])
            $taskQueue[$i][0] = TimerInit()
        EndIf

    Next
EndFunc   ;==>CheckTasks
;--------------------------------------------------------------------------------------------------------------------------------
Func RunTask($FilePath, $Param = "")
    Run(FileGetShortName(@AutoItExe) & ' "' & FileGetShortName($FilePath) & ($Param = "" ? '"' : '" ' & $Param))
EndFunc   ;==>RunTask
;--------------------------------------------------------------------------------------------------------------------------------

 

Edited by ioa747
add more

I know that I know nothing

Posted (edited)

Thanks for your input. 

I'm a bit nervous about multithreading. I tried it out once and I found it very complicated to no longer clearly know the sequence of the executed functions. Sometimes they use the same data, read and write, and I have the feeling that it doesn't need that. I'm completely happy if a task only happens one after the other and then, for example, a notification/alarm is delayed by a few seconds.

@ioa747 Thanks for the examples. I need to add tasks and delete them and Iam wondering what data type is makes the most sense for this. Arrays dont like to get changed its size, so maybe a map!? 

And I would like to have a sorted "queue" to debug. Maybe its even a good idea to work with dates, instead of timers?!

 

Ill start to fuse your example with mine and then Ill try out a different data type and time format. :)

 

*edit*

Iam here now:
 

#include <Array.au3>

; [0]Timestamp, [1] = Function, [2] = Parameter, [3] = Repeat, [4] = hTimer
Global $taskQueue[0][5]

AddTask(3000, "Task1")
AddTask(5000, "Task2", "", True)
AddTask(12000, "Task3")
AddTask(20000, "_Exit")
AddTask(12000, "TaskX", "param")

While Sleep(250)
    CheckTasks()
WEnd

Func CheckTasks()
    For $i = UBound($taskQueue) -1 To 0 Step -1

        If $taskQueue[$i][4] = 0 Then
            $taskQueue[$i][4] = TimerInit()
            ContinueLoop
        EndIf

        If TimerDiff($taskQueue[$i][4]) > $taskQueue[$i][0] Then
            ConsoleWrite("call " & $taskQueue[$i][1] & @CRLF)
            ConsoleWrite(" time:" & Round(TimerDiff($taskQueue[$i][4]) / 1000, 3) & " seconds")

            Call($taskQueue[$i][1], $taskQueue[$i][2] ? $taskQueue[$i][2] : "")

            If $taskQueue[$i][3] Then
                $taskQueue[$i][4] = TimerInit()
            Else
                _ArrayDelete($taskQueue, $i)
            EndIf
        EndIf

    Next
EndFunc

Func AddTask($iInterval, $sFunction, $vParameter = False, $bRepeat = False,$hTimer = 0)
    Local $aArray[1][5] = [[$iInterval, $sFunction, $vParameter, $bRepeat, $hTimer]]

    _ArrayAdd($taskQueue, $aArray)
EndFunc

Func _Exit()
    ConsoleWrite("Getting closed!" & @CRLF)
    Exit
EndFunc   ;==>_ExitFunction

Func Task1()
    ConsoleWrite(": Task1 completed!" & @CRLF)
EndFunc

Func Task2()
    ConsoleWrite(": Task2 completed!" & @CRLF)
EndFunc

Func Task3()
    ConsoleWrite(": Task3 completed!" & @CRLF)
EndFunc

Func TaskX($iParam)
    ConsoleWrite(": TaskX completed with " & $iParam & "!" & @CRLF)
EndFunc

 

Edited by Acanis
  • Solution
Posted (edited)

Ok, I did add the calculation of the tasks target time and a function to show me the queue. Iam pretty happy with that state...

Do you guys have ideas to improve it? Should I use another data type instead of an array? 

Do you see any weaknesses, apart from the fact that I deliberately do without multithreading?

#include <Array.au3>
#include <Date.au3>

ENUM $TASK_INTERVAL, $TASK_FUNCTION, $TASK_PARAMETER, $TASK_REPEAT, $TASK_HTIMER, $TASK_DATE
Global $taskQueue[0][6]

AddTask(3, "Task1")
AddTask(5, "Task2", "", True)
AddTask(7, "Task_")
DeleteTask("Task_")
AddTask(12, "Task3")
AddTask(20, "_Exit")
AddTask(12, "TaskX", "param")

OutputTaskQueue()

While Sleep(250)
    CheckTasks()
WEnd

Func CheckTasks()
    For $i = UBound($taskQueue) -1 To 0 Step -1
        If TimerDiff($taskQueue[$i][$TASK_HTIMER]) > $taskQueue[$i][$TASK_INTERVAL] Then
            ConsoleWrite("call " & $taskQueue[$i][$TASK_FUNCTION] & @CRLF)
            ConsoleWrite(" time:" & Round(TimerDiff($taskQueue[$i][$TASK_HTIMER]) / 1000, 3) & " seconds")

            Call($taskQueue[$i][$TASK_FUNCTION], $taskQueue[$i][$TASK_PARAMETER] ? $taskQueue[$i][$TASK_PARAMETER] : "")

            If $taskQueue[$i][$TASK_REPEAT] Then
                $taskQueue[$i][$TASK_HTIMER] = TimerInit()
            Else
                _ArrayDelete($taskQueue, $i)
            EndIf
        EndIf

    Next
EndFunc

Func AddTask($iInterval, $sFunction, $vParameter = False, $bRepeat = False, $hTimer = 0, $sDate = "") ; iInterval in Seconds
    $hTimer = $hTimer ? $hTimer : TimerInit()
    $iInterval *= 1000 ; from s to ms

    $sDate = _DateAdd('s', $iInterval / 1000, _NowCalc())

    Local $aArray[1][6] = [[$iInterval, $sFunction, $vParameter, $bRepeat, $hTimer, $sDate]]

    _ArrayAdd($taskQueue, $aArray)
EndFunc

Func DeleteTask($sFunction)
    For $i = UBound($taskQueue) - 1 To 0 Step -1
        If StringCompare($sFunction, $taskQueue[$i][$TASK_FUNCTION]) = 0 Then _ArrayDelete($taskQueue, $i)
    Next
EndFunc

Func _Exit()
    ConsoleWrite("Getting closed!" & @CRLF)
    Exit
EndFunc   ;==>_ExitFunction

Func Task1()
    ConsoleWrite(": Task1 completed!" & @CRLF)
EndFunc

Func Task2()
    ConsoleWrite(": Task2 completed!" & @CRLF)
EndFunc

Func Task3()
    ConsoleWrite(": Task3 completed!" & @CRLF)
EndFunc

Func TaskX($iParam)
    ConsoleWrite(": TaskX completed with " & $iParam & "!" & @CRLF)
EndFunc

Func Task_()
    ConsoleWrite(": [ERROR] Task_ shouldnt be executed!" & @CRLF)
EndFunc

Func OutputTaskQueue() ; does not consider repetitive tasks
    ConsoleWrite("Task Queue:" & @CRLF)

    For $i = 0 To UBound($taskQueue) - 1
        ConsoleWrite($taskQueue[$i][$TASK_DATE] & " | " & $taskQueue[$i][$TASK_FUNCTION] & @CRLF)
    Next

    ConsoleWrite(@CRLF)
EndFunc

 

*Edit* I added a DeleteTask-Function and an Enumn to make the array handling a little bit easier.

*Edit_2* I changed the CheckTask, so its using the AddTask and DeleteTask. So $TASK_DATE gets calculated again for repeatable tasks. Looks a little bit messy now... ^^'

#include <Array.au3>
#include <Date.au3>

ENUM $TASK_INTERVAL, $TASK_FUNCTION, $TASK_PARAMETER, $TASK_REPEAT, $TASK_HTIMER, $TASK_DATE
Global $taskQueue[0][6]

AddTask(3, "Task1")
AddTask(5, "Task2", "", True)
AddTask(7, "Task_")
DeleteTask("Task_")
AddTask(12, "Task3")
AddTask(20, "_Exit")
AddTask(12, "TaskX", "param")

OutputTaskQueue()

While Sleep(250)
    CheckTasks()
WEnd

Func CheckTasks()
    For $i = UBound($taskQueue) -1 To 0 Step -1
        If TimerDiff($taskQueue[$i][$TASK_HTIMER]) > $taskQueue[$i][$TASK_INTERVAL] Then
            ConsoleWrite("call " & $taskQueue[$i][$TASK_FUNCTION] & @CRLF)
            ConsoleWrite(" time:" & Round(TimerDiff($taskQueue[$i][$TASK_HTIMER]) / 1000, 3) & " seconds")

            Call($taskQueue[$i][$TASK_FUNCTION], $taskQueue[$i][$TASK_PARAMETER] ? $taskQueue[$i][$TASK_PARAMETER] : "")

            Local $aTempTaskParams[4] = [$taskQueue[$i][$TASK_INTERVAL] / 1000, $taskQueue[$i][$TASK_FUNCTION], $taskQueue[$i][$TASK_PARAMETER], $taskQueue[$i][$TASK_REPEAT]]
            DeleteTask($i)

            If $taskQueue[$i][$TASK_REPEAT] Then AddTask($aTempTaskParams[0], $aTempTaskParams[1], $aTempTaskParams[2], $aTempTaskParams[3])
        EndIf
    Next
EndFunc

Func AddTask($iInterval, $sFunction, $vParameter = False, $bRepeat = False, $hTimer = 0, $sDate = "") ; iInterval in Seconds
    $hTimer = $hTimer ? $hTimer : TimerInit()
    $iInterval *= 1000 ; from s to ms

    $sDate = _DateAdd('s', $iInterval / 1000, _NowCalc())

    Local $aArray[1][6] = [[$iInterval, $sFunction, $vParameter, $bRepeat, $hTimer, $sDate]]

    _ArrayAdd($taskQueue, $aArray)
    
    _ArraySort($taskQueue, 0, 0, 0, $TASK_DATE)
EndFunc

Func DeleteTask($vDeleteTask)
    If IsInt($vDeleteTask) Then ; delete a special ID
        _ArrayDelete($taskQueue, $vDeleteTask)
    Else ; Delete all functions with that name
        For $i = UBound($taskQueue) - 1 To 0 Step -1
            If StringCompare($vDeleteTask, $taskQueue[$i][$TASK_FUNCTION]) = 0 Then _ArrayDelete($taskQueue, $i)
        Next
    EndIf
EndFunc

Func _Exit()
    ConsoleWrite(": Getting closed!" & @CRLF)
    Exit
EndFunc   ;==>_ExitFunction

Func Task1()
    ConsoleWrite(": Task1 completed!" & @CRLF)
EndFunc

Func Task2()
    ConsoleWrite(": Task2 completed!" & @CRLF)
EndFunc

Func Task3()
    ConsoleWrite(": Task3 completed!" & @CRLF)
EndFunc

Func TaskX($iParam)
    ConsoleWrite(": TaskX completed with " & $iParam & "!" & @CRLF)
EndFunc

Func Task_()
    ConsoleWrite(": [ERROR] Task_ shouldnt be executed!" & @CRLF)
EndFunc

Func OutputTaskQueue() ; does not consider repetitive tasks
    ConsoleWrite("Task Queue:" & @CRLF)

    For $i = 0 To UBound($taskQueue) - 1
        ConsoleWrite($taskQueue[$i][$TASK_DATE] & " | " & $taskQueue[$i][$TASK_FUNCTION] & @CRLF)
    Next

    ConsoleWrite(@CRLF)
EndFunc

*Edit 3* Added the sorting into AddTask :)

Edited by Acanis
Posted (edited)
30 minutes ago, argumentum said:

...just passing by.
$hTimer = 0 ? TimerInit() : $hTimer is what you'd like to do.

Hmmm, there is no "check" if you write it like that?! The "checking part" is always "0". After I tried it anyway, it no longer worked.

 

Edited by Acanis

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...