Taskscheduler
We name the three tasks as follows:Socket Handler,Event Handler,Delay task.
The features of these three tasks are that the first two tasks will always exist after they are added to the execution queue, and the delay task will be immediately discarded after the execution is completed.
The socket handler is saved in the queue basictaskscheduler0: handlerset * fhandlers;
Event Handler is saved in the array basictaskscheduler0: taskfunc * ftriggeredeventhandlers [max_num_event_triggers;
The delay task is saved in the queue basictaskscheduler0: delayqueue fdelayqueue.
Let's take a look at the definition of the execution functions of the three types of tasks:
Socket handler is
Typedef void backgroundhandlerproc (void * clientdata, int mask );
Event Handler is
Typedef void taskfunc (void * clientdata );
Delay task is
Typedef void taskfunc (void * clientdata); // same as event handler.
Let's look at how to add three Task functions to the task scheduling object:
Delay task:
Void setbackgroundhandling (INT socketnum, int conditionset, backgroundhandlerproc * handlerproc, void * clientdata)
Event Handler is:
Eventtriggerid createeventtrigger (taskfunc * eventhandlerproc)
Delay task:
Tasktoken scheduledelayedtask (int64_t microseconds, taskfunc * proc, void * clientdata)
Why do I need those parameters when adding socket handler? Socketnum is required, because select socket (socketnum is the socket object returned by socket ). Conditionset is also required. It is used to indicate which loading state is checked by the socket during the SELECT statement and is readable? Writable? Or error? The two parameters proc and clientdata do not need to be mentioned (Do you really not understand ?). Let's look at the backgroundhandlerproc parameter. socketnum doesn't have to be explained. What is mask? It corresponds to the conditionset, but it indicates the result after select. For example, a socket may need to check its read/write status. Currently, it can only be read but cannot be written, in the mask, only the read bit is set.
Event Handler is contained in the array. The array size is fixed. It is 32 items and eventtriggerid is used to represent the items in the array. eventtriggerid is a 32-bit integer because the array is 32 items, so the nth position 1 in eventtriggerid indicates the nth entry in the corresponding array. The member variable ftriggersawaitinghandling is also of the eventtriggerid type. The bits set to 1 in the variable correspond to all the items to be processed in the array. This reduces memory and computing, but reduces readability. Besides, it is not flexible enough to support 32 or 64 items. Other items are not supported. The following is the function body
Eventtriggerid basictaskscheduler0: createeventtrigger (taskfunc * eventhandlerproc) <br/>{< br/> unsigned I = flastusedtriggernum; <br/> eventtriggerid mask = callback; </P> <p> // search for an unused item in the array and allocate eventhandlerproc to this item. <Br/> do {<br/> I = (I + 1) % max_num_event_triggers; <br/> mask >>=1; <br/> If (mask = 0) <br/> mask = 0x80000000; </P> <p> If (ftriggeredeventhandlers [I] = NULL) {<br/> // This trigger number is free; use it: <br/> ftriggeredeventhandlers [I] = eventhandlerproc; <br/> ftriggeredeventclientdatas [I] = NULL; // sanity </P> <p> flastusedtriggermask = mask; <br/> flastusedtriggernum = I; </P> <p> return mask; // allocation Successful. The returned value indicates the number of items <br/>}< br/>}while (I! = Flastusedtriggernum); // indicates that all entries in the array are in use in a loop </P> <p> // The returned result indicates that the query fails. <Br/> // all available event triggers are allocated; return 0 instead: <br/> return 0; <br/>}
You can see that up to 32 events are added, and the clientdata parameter is not input when you add events. This parameter is passed in when an event is triggered. See the following functions:
Void basictaskscheduler0: triggerevent (eventtriggerid, void * clientdata) <br/>{< br/> // first, record the "clientdata ": <br/> If (eventtriggerid = flastusedtriggermask) {<br/> // common-case optimization: Save clientdata directly <br/> using [flastusedtriggernum] = clientdata; <br/>} else {<br/> // search for the corresponding eventtriggerid from start to end and save clientdata <br/> eventtriggerid mask = 0x80000000; <B R/> for (unsigned I = 0; I <max_num_event_triggers; ++ I) {<br/> If (eventtriggerid & Mask )! = 0) {<br/> ftriggeredeventclientdatas [I] = clientdata; </P> <p> flastusedtriggermask = mask; <br/> flastusedtriggernum = I; <br/>}< br/> mask >>=1; <br/>}</P> <p> // then, note this event as being ready to be handled. <br/> // (note that because this function (unlike others in the library) <br/> // can be called from an external thread, we do this last, to <br/> // reduce the risk of a race conditio N.) <br/> // use ftriggersawaitinghandling to record the event handler to be responded in bit mask mode. <Br/> ftriggersawaitinghandling | = eventtriggerid; <br/>}Check that clientdata is passed in. This indicates that clientdata can be changed every time an event is triggered.
Now let's go back and see if singlestep () is clearer?
When a delay task is added, You need to input the number of microseconds (one second per million) of the task delay wait (the first parameter). Can this problem be understood? Hey. Analyze the following functions:
Tasktoken basictaskscheduler0: scheduledelayedtask (int64_t microseconds, taskfunc * proc, void * clientdata) <br/>{< br/> If (microseconds <0) <br/> microseconds = 0; <br/> // delayinterval indicates the time difference structure <br/> delayinterval timetodelay (long) (microseconds/1000000), (long) (microseconds % 1000000 )); <br/> // create an item in delayqueue <br/> alarmhandler * alarmhandler = new alarmhandler (Proc, clientdata, timetodelay); <br/> // Add delayqueue <br/> fdelayqueue. addentry (alarmhandler); <br/> // return the unique identifier of the delay task <br/> return (void *) (alarmhandler-> token ()); <br/>}</P> <p> the execution of delay tasks is performed in the fdelayqueue function. in handlealarm (), handlealarm () is implemented in the class delayqueue. Take a look at handlealarm (): <br/> void delayqueue: handlealarm () <br/>{< br/> // if the execution time of the first task is not reached, synchronize (re-calculate the wait time of each task ). <Br/> If (Head ()-> fdeltatimeremaining! = Delay_zero) <br/> synchronize (); <br/> // if the execution time of the first task is reached, execute the first task and delete it from the queue. <Br/> If (Head ()-> fdeltatimeremaining = delay_zero) {<br/> // This event is due to be handled: <br/> delayqueueentry * toremove = head (); <br/> removeentry (toremove); // do this first, in case handler accesses queue <br/> // executes the task, which is destroyed after execution. <Br/> toremove-> handletimeout (); <br/>}< br/>}It may be strange that other task queues search for the first item to be executed and then execute it. Here, simply execute the first item to finish the task. So it means that the first one is the one that should be executed most? That is, the Shortest Waiting Time? When adding a task, we should insert the new task and data wait time to the appropriate position instead of append it to the tail, right? If you guess it is correct, you must check how the fdelayqueue. addentry (alarmhandler) function is executed.
Void delayqueue: addentry (delayqueueentry * newentry) <br/>{< br/> // recalculate the wait time of each item <br/> synchronize (); </P> <p> // obtain the first entry <br/> delayqueueentry * cur = head (); <br/> // compare the wait time of the new item with each item in the cycle from start to end <br/> while (newentry-> fdeltatimeremaining> = cur-> fdeltatimeremaining) {<br/> // If the waiting time of the new item is longer than the waiting time of the current item, the waiting time of the current item is subtracted. <Br/> // that is, the waiting time is only the difference from the waiting time of the previous item, which saves the variable of the time when the record is inserted. <Br/> newentry-> fdeltatimeremaining-= cur-> fdeltatimeremaining; <br/> // next item <br/> cur = cur-> fnext; <br/>}</P> <p> // After the loop is completed, cur is the item found before it, put it in front <br/> cur-> fdeltatimeremaining-= newentry-> fdeltatimeremaining; </P> <p> // Add "newentry" to the queue, just before "cur": <br/> newentry-> fnext = cur; <br/> newentry-> fprev = cur-> fprev; <br/> cur-> fprev = newentry-> fprev-> fnext = newentry; <br/>}There is a problem. Why didn't the while loop judge whether it reached the final code? Are you sure you can find the item that is longer than the waiting time of the new item? Yes! The waiting time for the first item to be added is infinite, and this item will always exist in the queue.