X-Git-Url: https://wannabe.guru.org/gitweb/?a=blobdiff_plain;f=src%2Fpyutils%2Fparallelize%2Fthread_utils.py;h=bfbab233baf979204034b487e854dc478bac2eb1;hb=278d163705facc2276cd464414fb490ef6af50ab;hp=e3747fd51231474d04e3d83f62c70b25ce2eb6e4;hpb=c256f84c53368730ee07c26dc29d3a66456501c0;p=pyutils.git diff --git a/src/pyutils/parallelize/thread_utils.py b/src/pyutils/parallelize/thread_utils.py index e3747fd..bfbab23 100644 --- a/src/pyutils/parallelize/thread_utils.py +++ b/src/pyutils/parallelize/thread_utils.py @@ -126,7 +126,21 @@ def background_thread( class ThreadWithReturnValue(threading.Thread): """A thread whose return value is plumbed back out as the return - value of :meth:`join`. + value of :meth:`join`. Use like a normal thread:: + + def thread_entry_point(args): + # do something interesting... + return result + + if __name__ == "__main__": + thread = ThreadWithReturnValue( + target=thread_entry_point, + args=(whatever) + ) + thread.start() + result = thread.join() + print(f"thread finished and returned {result}") + """ def __init__( @@ -138,11 +152,35 @@ class ThreadWithReturnValue(threading.Thread): self._target = target self._return = None - def run(self): + def run(self) -> None: + """Create a little wrapper around invoking the real thread entry + point so we can pay attention to its return value.""" if self._target is not None: self._return = self._target(*self._args, **self._kwargs) - def join(self, *args): + def join(self, *args) -> Any: + """Wait until the thread terminates and return the value it terminated with + as the result of join. + + Like normal :meth:`join`, this blocks the calling thread until + the thread whose :meth:`join` is called terminates – either + normally or through an unhandled exception or until the + optional timeout occurs. + + When the timeout argument is present and not None, it should + be a floating point number specifying a timeout for the + operation in seconds (or fractions thereof). + + When the timeout argument is not present or None, the + operation will block until the thread terminates. + + A thread can be joined many times. + + :meth:`join` raises a RuntimeError if an attempt is made to join the + current thread as that would cause a deadlock. It is also an + error to join a thread before it has been started and + attempts to do so raises the same exception. + """ threading.Thread.join(self, *args) return self._return @@ -152,7 +190,7 @@ def periodically_invoke( stop_after: Optional[int], ): """ - Periodically invoke the decorated function. + Periodically invoke the decorated function on a background thread. Args: period_sec: the delay period in seconds between invocations @@ -170,13 +208,30 @@ def periodically_invoke( Usage:: + @periodically_invoke(period_sec=1.0, stop_after=3) + def hello(name: str) -> None: + print(f"Hello, {name}") + @periodically_invoke(period_sec=0.5, stop_after=None) def there(name: str, age: int) -> None: print(f" ...there {name}, {age}") - @periodically_invoke(period_sec=1.0, stop_after=3) - def hello(name: str) -> None: - print(f"Hello, {name}") + Usage as a decorator doesn't give you access to the returned stop event or + thread object. To get those, wrap your periodic function manually:: + + import pyutils.parallelize.thread_utils as tu + + def periodic(m: str) -> None: + print(m) + + f = tu.periodically_invoke(period_sec=5.0, stop_after=None)(periodic) + thread, event = f("testing") + ... + event.set() + thread.join() + + See also :mod:`pyutils.state_tracker`. + """ def decorator_repeat(func):