tkmilan.bg#
Model the background Event Loop processing.
Module Attributes
Normal priority for Event Loop Requests. |
Classes
|
Event Loop Callback definition. |
|
Event Loop Request. |
|
Event Loop Request, with priority. |
|
Event Loop Response. |
|
Event Loop Object |
- class tkmilan.bg.ELCallback(widget: Union[tk.Widget, tk.Tk], callback: Callable[[EventLoop, int | None, ELRes | bool], Any], idle: bool = True, afterMS: int = 0)#
Bases:
objectEvent Loop Callback definition.
- Parameters:
widget (Union[tk.Widget, tk.Tk]) – Widget to attach the
callbacktocallback (Callable[[EventLoop, int | None, ELRes | bool], Any]) – Function to call with the
response.idle (bool) – Choose
after_idleoverafter. Defaults toTrue. SeeTclafter and after idle documentation.afterMS (int) – If
idleisFalse, delay execution. SeeTclafter documentation.
- class tkmilan.bg.ELReq#
Bases:
objectEvent Loop Request.
Usually represents a task to be performed on an
EventLoop.See
EventLoopto see how to use this.
- class tkmilan.bg.ELReq_Priority(request: ~tkmilan.bg.ELReq, priority: int = 0, count: int = <factory>)#
Bases:
objectEvent Loop Request, with priority.
This is the actual object which is included on the
EventLoopinput queue. Wraps theELReqobject ignoring the ordering, so that it can be included on thePriorityQueueconsidering only thepriority, andcountto maintain the original ordering.- Parameters:
request (ELReq) – The Event Loop request object.
priority (int) – The request priority. Optional, see
NORMAL_PRIORITYfor the default value.count (int) –
The request count, used to maintain the order when these objects are pushed to a
PriorityQueue.See the Python PriorityQueue implementation notes. documentation. The default value does The Right Thing.
See also
This is to be used on a
PriorityQueue, see the Python documentation.
- class tkmilan.bg.ELRes#
Bases:
objectEvent Loop Response.
See
EventLoopto see how to use this.Note
If using an Event Bus, this should use a single subclass level, that is this should only be subclassed once, and those subclasses should not be subclassed further. See
RootWindow.register_eventbus_responsefor more information.
- class tkmilan.bg.EventLoop(*, qinput: PriorityQueue | None = None, qoutput: Queue | None = None, wcallback: None | Callable[[EventLoop, int | None, ELRes | bool], Any] | ELCallback = None, name: str | None = None, priorities: Mapping[Type[ELReq], int] | None = None, **kwargs: Any)#
Bases:
objectEvent Loop Object
Represent all Event Loop settings and necessary references.
An event loop is basically a big
while True:loop. The tasks to process areELReq_Priority(ELReqwith priority) objects, and that processing produces a stream of responses.There are two kinds of responses:
- A “signal”, index is
None, payload isbool. This signals the Event Loop started or stopped task processing.
- A “signal”, index is
- A “response”, index is
int, payload isELRes. The index indicates the count of the current processing task.
- A “response”, index is
Internally uses the
threadingfunctions. This means the threading model is cooperative, that is, the task processing functions must yield their time, not hog the processing time for themselves.The event loop itself is single-threaded, meaning there’s only at most a single task is being executed. The input queue is a
PriorityQueue, which means the request order is not fixed, there are only defaults. The requests are processed in priority order (lowest first).You can choose between two different response parsing behaviours, depending on the arguments you give:
qoutput: Define the response output as a queue, possibly given from outside.wcallback: Define some aTkcallback function to be called.Even though
Tkis not thread-safe, some specific functions are, and this is used to “merge” the event process loop with the existingTkevent loop. SeeELCallbackfor more information on how those functions are defined.
Note that
qoutputis the default, when you do not choose one or the other.Avoid using
time.sleepdirectly when implementing the tasks. Useisleep/usleepfor precise control over interruptible behaviour.- Parameters:
qinput (PriorityQueue) –
PriorityQueuewithELReq_Priorityto consume. Optional, creates a new queue when not given.qoutput (Queue | None) –
QueuewithELResto produce. Optional, creates a new queue when not given. See above for the interaction withwcallback.wcallback (ELCallback | None) –
Callback Configurationobject. Optional. See above for the interaction withqoutput.name (str | None) – The Event Loop thread name. Optional, overrides
nameif given. SeetNamefor the final name.priorities (Mapping[Type[ELReq], int] | None) – Define Event Loop request default priorities. Optional, overrides
prioritiesif given.
All other keyword arguments are passed to the
setup_eventloopfunction.Note
This object should be created on the Main Thread, not from any sub-thread. This is enforced on debug mode.
Note
In case
qoutputis chosen, the mainTkprocessing loop forresponsesmust implement something like this, using anInterval:import tkmilan from tkmilan.model import Interval class EventLoop(tkmilan.bg.EventLoop): # Implement class RW(tkmilan.RootWindow): def setup_eventloops(self): return {'sleep': EventLoop(name='sleep')} def setup_adefaults(self): self.start_eventloops() for _, el in self.get_eventloops(bus=False): logger.debug('# EventLoop tName=%s', eventloop.tName) # Process Events every second Interval(self, lambda: self.onProcessEventsEL(el), 1000, immediate=True) def onProcessEventsEL(self, eventloop: bg.EventLoop): for ridx, response in eventloop.responses(chunk=10): # Or other amounts if ridx is None: logger.debug('eventloop=%s Signal: %s', eventloop.tName, response) else: logger.debug('eventloop=%s Response: %r', eventloop.tName, response)
- cntRequests() int#
Return the approximate size of the request queue.
This indicates unprocessed tasks. See the upstream function
qsize.Note
This should be called from the Main Thread, not from any sub-thread. It can be called from any thread, including the
Event Loop Thread, but that is usually a logic error.
- cntResponses() int | None#
Return the approximate size of the response queue, if exists.
This indicates unprocessed responses. See the upstream function
qsize.- Returns:
Returns
Noneif there is no output queue (connected to aTkevent loop directly).- Return type:
int | None
Note
This should be called from the Main Thread, not from any sub-thread. It can be called from any thread, including the
Event Loop Thread, but that is usually a logic error.
- is_paused() bool#
Check if the event loop is paused.
This means a dead event loop that has some requests to be processed.
This means the corresponding thread was alive at some point, but no longer. It can be
restartedonce again, to keep processing the remaining events.See also
The
is_runningfunction should be used on most situations.
- is_running() bool#
Check if the Event Loop is running.
This means the thread is ready to process tasks.
Once the thread is
stopped, it stops running, but the thread itself might remainalivefor a while for cleanup.
- isleep(duration: float) bool#
Suspend the thread execution for a while, interruptible.
This suspension can be interrupted by calling
stop.Note
This is called from the
Event Loop Thread, when implementing the processing functions. Seeprocess.Do not use this in other contexts.
See also
For a non-interuptible thread suspension, see
usleep.
- process(task: ELReq, priority: int)#
Process a single task, with a priority.
Defaults to doing nothing, subclasses should redefine this. See
singledispatchmethodfor a possible solution for polymorphism based onELReqsubclasses.The
cntProcessedcounter is unique for each task during this call, and it is attached to the responses queued during this call. This means it can be used to match request and respond, even though everything happens asynchronously. To do that, save that result on the response, and compare it withridx.Do not use this directly, see
queue.Note
This is called from the
Event Loop Thread, automatically.
- queue(task: ELReq, *, priority: int | None = None)#
Queue a new task on the event loop, optionally with a priority.
Append a new
taskto the Event Loop. You can set a priority for the task, or leave it with the default priority:prioritiesand the ultimateNORMAL_PRIORITYfallback.It is possible to queue tasks into a paused event loop. This will be processed once the event loop restarts.
- Parameters:
task (ELReq) – Task to queue
priority (int | None) – Request priority. Optional, defaults to
prioritiesandNORMAL_PRIORITY.
Note
This should be called from the Main Thread, not from any sub-thread. It can be called from any thread, including the
Event Loop Thread, but that is usually a logic error.
- respond(response: ELRes)#
Add a new response on the output.
Append a new
responseon the Event Loop output queue, or equivalent.Note
This is called from the
Event Loop Thread, when implementing the processing functions. Seeprocess.Do not use this in other contexts.
- responses(chunk: int = 10) Iterable[Tuple[int | None, bool | ELRes]]#
Retrieve responses from the event loop.
- Parameters:
chunk (int) – Retrive at most this amount of responses.
- Returns:
This is a generator that produces data tuples. See
EventLoopfor the kinds of responses produced here, the format is a tuple(index, payload).- Return type:
Iterable[Tuple[int | None, bool | ELRes]]
Note
This should be called from the Main Thread, not from any sub-thread. It can be called from any thread, including the
Event Loop Thread, but that is usually a logic error.
- run()#
Run the EventLoop.
This is the target function for the
thread object.Do not use this directly, see
start.
- setup_eventloop(**kwargs) None#
Setup the EventLoop object, with the other keyword arguments.
Defaults to doing nothing, subclasses should redefine this.
- setup_process_respond(response_type: Type[ELRes]) Callable[[ELReq, int], None]#
Setup a basic
processfunction, with static responses.Setup a
processfunction with a simple log, with a responseresponse_type().Note
This is mostly useful for testing, the correct way to implement this is to subclass the
EventLoopclass.
- signal(signal: bool)#
Signal a new internal event on the output.
Should append a
boolsignal on the output queue, or equivalent.Note
This is called from the
Event Loop Thread, when implementing the processing functions. Seeprocess.Do not use this in other contexts.
- start(*, daemon: bool = True) Thread#
Start executing the Event Loop, in a background thread.
There will be a single thread processing event at a time. If you try to “start” a running Event Loop, this raises an
exception.- Parameters:
daemon (bool) – Set the
daemonoption for thread creation. Defaults toTrue.
Note
This should be called from the Main Thread, not from any sub-thread. It can be called from any thread, including the
Event Loop Thread, but that is usually a logic error.
- stop(*, join: bool = False)#
Stop execution the Event Loop, ASAP.
If you try to “stop” a non-running Event Loop, this raises an
exception.Since the threading model is cooperative, the currently executing task (if any) is not interrupted. The only exception is the
isleepprocess, that is interrupted.- Parameters:
join (bool) – Wait for the thread to finish. Defaults to
False, fully asynchronous behaviour.
Note
This should be called from the Main Thread, not from any sub-thread. It can be called from any thread, including the
Event Loop Thread, but that is usually a logic error.
- toggle() Thread | None#
Toggle Event Loop execution.
If
is_running,stopit, otherwisestartit. Useful to have a single button for controlling the Event Loop execution state.- Returns:
Returns
Noneif the Event Loop was stopped, or the next Event Loop Thread Object otherwise.- Return type:
Thread | None
- usleep(duration: float) bool#
Suspend the thread execution for a while, interruptible.
This suspension cannot be interrupted; when calling
stop, the event loop will only stop after this suspension ends.Note
This is called from the
Event Loop Thread, when implementing the processing functions. Seeprocess.Do not use this in other contexts.
See also
For an interuptible thread suspension, see
isleep.
- cntProcessed: int#
Counter for correctly processed
ELReqtasks.This is incremented right after the processing function run successfully. It can be safely used from within the
processfunction, in the Event Loop Thread, but not from other threads (no locking is implemented).
- name: str | None = None#
Thread Name, for all instances in the class.
This is the per-class setting, can be overriden. See
tNamefor the per-instance name.
- priorities: Mapping[Type[ELReq], int] | None = None#
Event Loop Request priorities.
Optional. This is the per-class setting, can be overriden.
- tName: str#
Thread Name
- thread: Thread | None#
Event Loop Thread Object.
Nonewhen the event loop is not alive, points to the Event LoopThreadotherwise. Seeis_running.
- tkmilan.bg.NORMAL_PRIORITY: int = 0#
Normal priority for Event Loop Requests.
See also
See
ELReq_PriorityandEventLoop.queuefor usage.