From 0326f6f66d295c039f5fda625ccff32bfdd07d8e Mon Sep 17 00:00:00 2001 From: Josef Date: Fri, 19 Mar 2021 22:57:49 +0100 Subject: [PATCH 1/5] Added Non blocking Scheduling doc added documentation regarding scheduling task and corutines in non-blocking loop --- AUTHORS.rst | 4 +- docs/async-support.rst | 92 ++++++++++++++++++++++++++++++++++++++++++ docs/faq.rst | 8 ++++ docs/index.rst | 3 +- 4 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 docs/async-support.rst diff --git a/AUTHORS.rst b/AUTHORS.rst index 0d0620fd..b2c3f873 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -30,6 +30,8 @@ Thanks to all the wonderful folks who have contributed to schedule over the year - zcking - Martin Thoma - ebllg + - fredthomsen - biggerfisch -- sosolidkk \ No newline at end of file +- sosolidkk +- 07pepa diff --git a/docs/async-support.rst b/docs/async-support.rst new file mode 100644 index 00000000..74debea6 --- /dev/null +++ b/docs/async-support.rst @@ -0,0 +1,92 @@ +Nonblocking scheduling with coroutines +====================================== +Fist is good to explain what we mean by nonblocking scheduling. +Most of time during scheduling will be spent in spinning on time.sleep + + +What it allows +------------------ +This allows better handling signals during long sleeps (day long sleeps are relatively fine) +and exiting scheduling loop right away after signal reliably without need of multiple threads. +This may lead to better usage of cpu (due just in time wakeup for next task). +It can also allow running scheduler in asyncio application without blocking it and can force scheduling loop to run. +It also allows nonblocking execution of coroutines. + +How? +---- +This code will only run on Wednesday regular task and Sunday coroutine task. +Nothing is running in between but code not hangs on system signals and exits loop (on one thread that is impossible without coroutines). + +.. code-block:: python + import schedule + import asyncio + + print(schedule.every().wednesday.do(lambda: print("It is Wednesday..."))) + + + def wrap_coroutine(c): + asyncio.create_task(c()) # coroutine must always be wrapped in create task + + + async def something_useful(): + await asyncio.sleep(1) # some useful async function + print("is is sunday") + + + print(schedule.every().sunday.do(lambda: wrap_coroutine(something_useful))) + + terminateEvent = None + def setup(): #needs to run on asyncio loop + import signal + import platform + + global terminateEvent + terminateEvent = asyncio.Event() #needs to run on asyncio loop + + def terminate(signum): + print(f"sayonara, I received {signum}") + terminateEvent.set() # kill loop + + isLinux = platform.system() == "Linux" or platform.system() == "Darwin" # only unix has add_signal_handler + + if isLinux: + loop = asyncio.get_running_loop() #needs to run on asyncio loop + + def linux_handler(signum): + def handler(): + terminate(signum) + + loop.add_signal_handler(signum, handler) + + handleSignal = linux_handler + else: + def windows_handler(signum): + def wrapper(sig, frame): + terminate(sig) + + signal.signal(signum, wrapper) + + handleSignal = windows_handler + + handleSignal(signal.SIGINT) + + + async def kill_sleep(seconds): + try: + return await asyncio.wait_for(terminateEvent.wait(), timeout=seconds) + except asyncio.TimeoutError: + return False + + + async def main(): + setup() + while not await kill_sleep(schedule.idle_seconds()): # will sleep until next + schedule.run_pending() + + + asyncio.run(main()) + + +DrawBacks +---------- +Only drawback is precision of asyncio.wait_for timeout. \ No newline at end of file diff --git a/docs/faq.rst b/docs/faq.rst index 81519cff..e9bd9173 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -68,6 +68,14 @@ How to continuously run the scheduler without blocking the main thread? ----------------------------------------------------------------------- :doc:`Background Execution`. +Better handling os signals during time.sleep +----------------------------------------- +:doc:`non blocking scheduling` + +Does schedule support coroutines? +------------------------------- +:doc:`yes ` + Another question? ----------------- If you are left with an unanswered question, `browse the issue tracker `_ to see if your question has been asked before. diff --git a/docs/index.rst b/docs/index.rst index b2327ea6..b4f5b8f1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,11 +16,12 @@ Python job scheduling for humans. Run Python functions (or any other callable) p - A simple to use API for scheduling jobs, made for humans. - In-process scheduler for periodic jobs. No extra processes needed! - Very lightweight and no external dependencies. +- Supports :doc:`non blocking/async scheduling` - Excellent test coverage. - Tested on Python 3.6, 3.7, 3.8 and 3.9 -:doc:`Example ` +:doc:`Example` ------------------------- .. code-block:: bash From 9e79dbb4b365172a8bdf5d0649378e5ac2123cce Mon Sep 17 00:00:00 2001 From: sijmenhuizenga Date: Sun, 28 Mar 2021 00:45:51 +0100 Subject: [PATCH 2/5] Simplify examples, shorten text and renamed 'async-support.rst' to 'asyncio.rst --- docs/async-support.rst | 92 ------------------------------------------ docs/asyncio.rst | 39 ++++++++++++++++++ docs/faq.rst | 10 ++--- docs/index.rst | 3 +- 4 files changed, 45 insertions(+), 99 deletions(-) delete mode 100644 docs/async-support.rst create mode 100644 docs/asyncio.rst diff --git a/docs/async-support.rst b/docs/async-support.rst deleted file mode 100644 index 74debea6..00000000 --- a/docs/async-support.rst +++ /dev/null @@ -1,92 +0,0 @@ -Nonblocking scheduling with coroutines -====================================== -Fist is good to explain what we mean by nonblocking scheduling. -Most of time during scheduling will be spent in spinning on time.sleep - - -What it allows ------------------- -This allows better handling signals during long sleeps (day long sleeps are relatively fine) -and exiting scheduling loop right away after signal reliably without need of multiple threads. -This may lead to better usage of cpu (due just in time wakeup for next task). -It can also allow running scheduler in asyncio application without blocking it and can force scheduling loop to run. -It also allows nonblocking execution of coroutines. - -How? ----- -This code will only run on Wednesday regular task and Sunday coroutine task. -Nothing is running in between but code not hangs on system signals and exits loop (on one thread that is impossible without coroutines). - -.. code-block:: python - import schedule - import asyncio - - print(schedule.every().wednesday.do(lambda: print("It is Wednesday..."))) - - - def wrap_coroutine(c): - asyncio.create_task(c()) # coroutine must always be wrapped in create task - - - async def something_useful(): - await asyncio.sleep(1) # some useful async function - print("is is sunday") - - - print(schedule.every().sunday.do(lambda: wrap_coroutine(something_useful))) - - terminateEvent = None - def setup(): #needs to run on asyncio loop - import signal - import platform - - global terminateEvent - terminateEvent = asyncio.Event() #needs to run on asyncio loop - - def terminate(signum): - print(f"sayonara, I received {signum}") - terminateEvent.set() # kill loop - - isLinux = platform.system() == "Linux" or platform.system() == "Darwin" # only unix has add_signal_handler - - if isLinux: - loop = asyncio.get_running_loop() #needs to run on asyncio loop - - def linux_handler(signum): - def handler(): - terminate(signum) - - loop.add_signal_handler(signum, handler) - - handleSignal = linux_handler - else: - def windows_handler(signum): - def wrapper(sig, frame): - terminate(sig) - - signal.signal(signum, wrapper) - - handleSignal = windows_handler - - handleSignal(signal.SIGINT) - - - async def kill_sleep(seconds): - try: - return await asyncio.wait_for(terminateEvent.wait(), timeout=seconds) - except asyncio.TimeoutError: - return False - - - async def main(): - setup() - while not await kill_sleep(schedule.idle_seconds()): # will sleep until next - schedule.run_pending() - - - asyncio.run(main()) - - -DrawBacks ----------- -Only drawback is precision of asyncio.wait_for timeout. \ No newline at end of file diff --git a/docs/asyncio.rst b/docs/asyncio.rst new file mode 100644 index 00000000..7b02da5b --- /dev/null +++ b/docs/asyncio.rst @@ -0,0 +1,39 @@ +Asyncio & Coroutines +==================== + +This example shows how to schedule an async job on the asyncio event loop. + +.. code-block:: python + + import schedule + import asyncio + + async def job_async(): + print("I am an async job") + await asyncio.sleep(1) + + schedule.every(5).seconds.do(lambda: asyncio.create_task(job_async())) + + async def scheduler(): + while True: + await asyncio.sleep(1) + schedule.run_pending() + + asyncio.run(scheduler()) + +The call to ``asyncio.create_task()`` submits the ``job_async`` coroutine to the asyncio event loop. +it may or may not run immediately, depending on the how busy the event loop is. + +Sleeping the right amount of time +--------------------------------- + +The ``scheduler()`` can be optimized by sleeping exactly the amount of time until the next job is scheduled to run: + +.. code-block:: python + + async def scheduler(): + while True: + await asyncio.sleep(schedule.idle_seconds()) + schedule.run_pending() + +Keep in mind that if a new job is scheduled while sleeping, the new job's earliest run is whenever the sleep finishes. diff --git a/docs/faq.rst b/docs/faq.rst index e9bd9173..27a993fd 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -68,13 +68,11 @@ How to continuously run the scheduler without blocking the main thread? ----------------------------------------------------------------------- :doc:`Background Execution`. -Better handling os signals during time.sleep +Does schedule support asyncio coroutines? ----------------------------------------- -:doc:`non blocking scheduling` - -Does schedule support coroutines? -------------------------------- -:doc:`yes ` +Schedule does not accept coroutines as jobs directly. +However, using using ``asyncio.create_task`` you able to schedule async jobs. +See :doc:`this page ` for an example. Another question? ----------------- diff --git a/docs/index.rst b/docs/index.rst index b4f5b8f1..0f4c7c7d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,7 +16,7 @@ Python job scheduling for humans. Run Python functions (or any other callable) p - A simple to use API for scheduling jobs, made for humans. - In-process scheduler for periodic jobs. No extra processes needed! - Very lightweight and no external dependencies. -- Supports :doc:`non blocking/async scheduling` +- Works with :doc:`asyncio coroutines` - Excellent test coverage. - Tested on Python 3.6, 3.7, 3.8 and 3.9 @@ -76,6 +76,7 @@ Read More examples background-execution parallel-execution + asyncio exception-handling logging multiple-schedulers From 5edb795019d90d953dc11c6fc619293475f843ba Mon Sep 17 00:00:00 2001 From: Josef Date: Sat, 10 Apr 2021 23:32:50 +0200 Subject: [PATCH 3/5] Added/improved asyncio documentation --- docs/{asyncio.rst => async-support.rst} | 18 ++- docs/asyncio-event-signals.rst | 158 ++++++++++++++++++++++++ docs/examples.rst | 10 +- docs/faq.rst | 25 +++- 4 files changed, 199 insertions(+), 12 deletions(-) rename docs/{asyncio.rst => async-support.rst} (65%) create mode 100644 docs/asyncio-event-signals.rst diff --git a/docs/asyncio.rst b/docs/async-support.rst similarity index 65% rename from docs/asyncio.rst rename to docs/async-support.rst index 7b02da5b..6358e35f 100644 --- a/docs/asyncio.rst +++ b/docs/async-support.rst @@ -4,7 +4,6 @@ Asyncio & Coroutines This example shows how to schedule an async job on the asyncio event loop. .. code-block:: python - import schedule import asyncio @@ -27,13 +26,22 @@ it may or may not run immediately, depending on the how busy the event loop is. Sleeping the right amount of time --------------------------------- -The ``scheduler()`` can be optimized by sleeping exactly the amount of time until the next job is scheduled to run: +The ``scheduler()`` can be optimized by sleeping *exactly* [1]_ the amount of time until the next job is scheduled to run: .. code-block:: python - async def scheduler(): - while True: - await asyncio.sleep(schedule.idle_seconds()) + seconds = schedule.idle_seconds() + while seconds is not None: + if seconds > 0: + # sleep exactly the right amount of time + await asyncio.sleep(seconds) schedule.run_pending() +asyncio.run(scheduler()) Keep in mind that if a new job is scheduled while sleeping, the new job's earliest run is whenever the sleep finishes. + + + + +.. rubric:: Footnotes +.. [1] `asyncio.sleep` may sleep little more then expected, depending on loop implementation of loop and system load \ No newline at end of file diff --git a/docs/asyncio-event-signals.rst b/docs/asyncio-event-signals.rst new file mode 100644 index 00000000..617eaa75 --- /dev/null +++ b/docs/asyncio-event-signals.rst @@ -0,0 +1,158 @@ +Asyncio examples with events and signals +======================================== + + +Those examples how python asyncio facilities may interact with schedule. +It will touch Terminate-able loop, handling signals and changing sleep time due scheduling during sleep. + +Terminate-able loop +------------- +.. code-block:: python + import schedule + import asyncio + + event=None + + async def terminator(): + print("I will be back") + await asyncio.sleep(1) + event.set() + + schedule.every(5).seconds.do(lambda: asyncio.create_task(terminator())) + + async def sleep(seconds): + if seconds is None: + return True #no more jobs, terminate + if seconds==0: + return False + try: + return await asyncio.wait_for(event.wait(), timeout=seconds) + except asyncio.TimeoutError: + return False + + + async def scheduler(): + global event + event=asyncio.Event() + while not await sleep(schedule.idle_seconds()): # will sleep until next or exits + schedule.run_pending() + + + asyncio.run(scheduler()) + + Save Scheduling during sleep +----------------------------- +.. code-block:: python + import schedule + import asyncio + + event = None + + + # this can even be called from another threads but you must have loop that schedule loop is running on + # it can be done like loop.call_soon_threadsafe(async_schedule,schedule_call) + # this is threadsafe thanks to cooperative multitasking (newer multiple async_schedule are running at same time) + def async_schedule(*schedule_calls): + next_run = schedule.next_run() + for call in schedule_calls: + call() + if next_run != schedule.next_run(): + event.set() + + + def submit_once(): + async def late_submit(): + await asyncio.sleep(1)#submiting during sleep + print("submiting new task") + async_schedule(lambda: schedule.every(2).seconds.do(lambda: print("new task running"))) + + asyncio.create_task(late_submit()) + return schedule.CancelJob + + + schedule.every(1).seconds.do(submit_once) + + + schedule.every(8).seconds.do(lambda: print("keep loop working")) + + + async def scheduler(): + global event + event = asyncio.Event() + interrupt_task = asyncio.create_task(event.wait()) + seconds = schedule.idle_seconds() + while seconds is not None:# if seconds is None there are no other jobs + if seconds > 0: + done, _ = await asyncio.wait({interrupt_task}, timeout=seconds) + if interrupt_task in done: # someone set event + event.clear() + interrupt_task = asyncio.create_task(event.wait()) #start monitoring again + seconds = schedule.idle_seconds() + continue # re-schedule + + + schedule.run_pending() + seconds = schedule.idle_seconds() + + + asyncio.run(scheduler()) + + + +Termination on os signals +-------------------------- +.. code-block:: python + import schedule + import asyncio + print(schedule.every().wednesday.do(lambda: print("It is Wednesday..."))) + + print(schedule.every().sunday.do(lambda: asyncio.create_task(something_useful))) + terminateEvent = None + def setup(): #needs to run on asyncio loop and setup on main thread + import signal + global terminateEvent + terminateEvent = asyncio.Event() #needs to run on asyncio loop + def terminate(signum): + print(f"sayonara, I received {signum}") + terminateEvent.set() # kill loop + + loop = asyncio.get_running_loop() #needs to run on asyncio loop + def handler(signum): # this is universal + global handler # allows self modifying code + try: #runtime probe if this python have add_signal_handler defined + loop.add_signal_handler(signum, lambda:terminate(signum)) + def simplified(signum):#yay it has lets remove try catch to simplify it on next run + loop.add_signal_handler(signum, lambda:terminate(signum)) + handler=simplified + except NotImplementedError: #not defined lets use self modifying code to execute correct version on next run + def backup_handler(signum): + orig_impl=signal.getsignal(signalnum) + if not orig_impl: + orig_impl = signal.SIG_IGN + def wrapper(sig, frame): + terminate(sig) + orig_impl(sig, frame) + signal.signal(signum, wrapper) + handler = backup_handler + backup_handler(signum) + finally: + handler.__name__="handler" + + handler(signal.SIGINT) + + async def sleep(seconds): + if seconds is None: + return True #no more jobs, terminate + if seconds==0: + return False + try: + return await asyncio.wait_for(event.wait(), timeout=seconds) + except asyncio.TimeoutError: + return False + + async def main(): + setup() + while not await sleep(schedule.idle_seconds()): + schedule.run_pending() + asyncio.run(main()) + diff --git a/docs/examples.rst b/docs/examples.rst index 397fc913..daf17b98 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -260,15 +260,13 @@ Returns ``None`` if no jobs are scheduled. schedule.every(5).seconds.do(job) - while 1: - n = schedule.idle_seconds() - if n is None: - # no more jobs - break - elif n > 0: + seconds = schedule.idle_seconds() + while seconds is not None: #None= no more jobs + if n > 0: # sleep exactly the right amount of time time.sleep(n) schedule.run_pending() + seconds = schedule.idle_seconds() Run all jobs now, regardless of their scheduling diff --git a/docs/faq.rst b/docs/faq.rst index 27a993fd..48e4ef03 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -70,10 +70,33 @@ How to continuously run the scheduler without blocking the main thread? Does schedule support asyncio coroutines? ----------------------------------------- -Schedule does not accept coroutines as jobs directly. +Schedule does not accept coroutines as jobs directly yet. However, using using ``asyncio.create_task`` you able to schedule async jobs. See :doc:`this page ` for an example. + +Can i use threading.Event.wait as means of scheduling and Terminating loop with loong sleep? +---------------------------------------------------------------------------------------- +Only in cases when you are running scheduling loop in separate thread +you also must make sure that you are not blocking main thread with joins/waiting on event etc. +This is because python will not handle any signals +However you can use ``asyncio.Event`` See :doc:`this page ` + +My long sleeping scheduler is not reacting on ctr+c +--------------------------------------------------- +In your code or other dependencies there is somewhere overridden signal handler +without also calling previous implementation that means KeyboardInterrupt is no longer thrown +so you will have to override their signal handler with your that exits loop and execute their implementation if needed. + +or you may be blocking main thread. + +See :doc:`this page `. + +What happens when my computer hibernates during time.sleep/wait +---------------------------------------------------------------- +Depending on python version it may behave erratically in undocumented ways `see `_ +in future this may be resolved by asyncio + Another question? ----------------- If you are left with an unanswered question, `browse the issue tracker `_ to see if your question has been asked before. From f46d94ec4ba42fecea5a62ba776b2c0671312ba2 Mon Sep 17 00:00:00 2001 From: Sijmen Huizenga Date: Sat, 22 May 2021 15:18:34 +0200 Subject: [PATCH 4/5] Simplify coroutine docs --- AUTHORS.rst | 1 - docs/asyncio-event-signals.rst | 158 ------------------------ docs/{async-support.rst => asyncio.rst} | 12 +- docs/faq.rst | 20 +-- docs/index.rst | 2 +- 5 files changed, 9 insertions(+), 184 deletions(-) delete mode 100644 docs/asyncio-event-signals.rst rename docs/{async-support.rst => asyncio.rst} (87%) diff --git a/AUTHORS.rst b/AUTHORS.rst index b2c3f873..d85032f5 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -30,7 +30,6 @@ Thanks to all the wonderful folks who have contributed to schedule over the year - zcking - Martin Thoma - ebllg - - fredthomsen - biggerfisch - sosolidkk diff --git a/docs/asyncio-event-signals.rst b/docs/asyncio-event-signals.rst deleted file mode 100644 index 617eaa75..00000000 --- a/docs/asyncio-event-signals.rst +++ /dev/null @@ -1,158 +0,0 @@ -Asyncio examples with events and signals -======================================== - - -Those examples how python asyncio facilities may interact with schedule. -It will touch Terminate-able loop, handling signals and changing sleep time due scheduling during sleep. - -Terminate-able loop -------------- -.. code-block:: python - import schedule - import asyncio - - event=None - - async def terminator(): - print("I will be back") - await asyncio.sleep(1) - event.set() - - schedule.every(5).seconds.do(lambda: asyncio.create_task(terminator())) - - async def sleep(seconds): - if seconds is None: - return True #no more jobs, terminate - if seconds==0: - return False - try: - return await asyncio.wait_for(event.wait(), timeout=seconds) - except asyncio.TimeoutError: - return False - - - async def scheduler(): - global event - event=asyncio.Event() - while not await sleep(schedule.idle_seconds()): # will sleep until next or exits - schedule.run_pending() - - - asyncio.run(scheduler()) - - Save Scheduling during sleep ------------------------------ -.. code-block:: python - import schedule - import asyncio - - event = None - - - # this can even be called from another threads but you must have loop that schedule loop is running on - # it can be done like loop.call_soon_threadsafe(async_schedule,schedule_call) - # this is threadsafe thanks to cooperative multitasking (newer multiple async_schedule are running at same time) - def async_schedule(*schedule_calls): - next_run = schedule.next_run() - for call in schedule_calls: - call() - if next_run != schedule.next_run(): - event.set() - - - def submit_once(): - async def late_submit(): - await asyncio.sleep(1)#submiting during sleep - print("submiting new task") - async_schedule(lambda: schedule.every(2).seconds.do(lambda: print("new task running"))) - - asyncio.create_task(late_submit()) - return schedule.CancelJob - - - schedule.every(1).seconds.do(submit_once) - - - schedule.every(8).seconds.do(lambda: print("keep loop working")) - - - async def scheduler(): - global event - event = asyncio.Event() - interrupt_task = asyncio.create_task(event.wait()) - seconds = schedule.idle_seconds() - while seconds is not None:# if seconds is None there are no other jobs - if seconds > 0: - done, _ = await asyncio.wait({interrupt_task}, timeout=seconds) - if interrupt_task in done: # someone set event - event.clear() - interrupt_task = asyncio.create_task(event.wait()) #start monitoring again - seconds = schedule.idle_seconds() - continue # re-schedule - - - schedule.run_pending() - seconds = schedule.idle_seconds() - - - asyncio.run(scheduler()) - - - -Termination on os signals --------------------------- -.. code-block:: python - import schedule - import asyncio - print(schedule.every().wednesday.do(lambda: print("It is Wednesday..."))) - - print(schedule.every().sunday.do(lambda: asyncio.create_task(something_useful))) - terminateEvent = None - def setup(): #needs to run on asyncio loop and setup on main thread - import signal - global terminateEvent - terminateEvent = asyncio.Event() #needs to run on asyncio loop - def terminate(signum): - print(f"sayonara, I received {signum}") - terminateEvent.set() # kill loop - - loop = asyncio.get_running_loop() #needs to run on asyncio loop - def handler(signum): # this is universal - global handler # allows self modifying code - try: #runtime probe if this python have add_signal_handler defined - loop.add_signal_handler(signum, lambda:terminate(signum)) - def simplified(signum):#yay it has lets remove try catch to simplify it on next run - loop.add_signal_handler(signum, lambda:terminate(signum)) - handler=simplified - except NotImplementedError: #not defined lets use self modifying code to execute correct version on next run - def backup_handler(signum): - orig_impl=signal.getsignal(signalnum) - if not orig_impl: - orig_impl = signal.SIG_IGN - def wrapper(sig, frame): - terminate(sig) - orig_impl(sig, frame) - signal.signal(signum, wrapper) - handler = backup_handler - backup_handler(signum) - finally: - handler.__name__="handler" - - handler(signal.SIGINT) - - async def sleep(seconds): - if seconds is None: - return True #no more jobs, terminate - if seconds==0: - return False - try: - return await asyncio.wait_for(event.wait(), timeout=seconds) - except asyncio.TimeoutError: - return False - - async def main(): - setup() - while not await sleep(schedule.idle_seconds()): - schedule.run_pending() - asyncio.run(main()) - diff --git a/docs/async-support.rst b/docs/asyncio.rst similarity index 87% rename from docs/async-support.rst rename to docs/asyncio.rst index 6358e35f..804cd37c 100644 --- a/docs/async-support.rst +++ b/docs/asyncio.rst @@ -4,6 +4,7 @@ Asyncio & Coroutines This example shows how to schedule an async job on the asyncio event loop. .. code-block:: python + import schedule import asyncio @@ -21,7 +22,7 @@ This example shows how to schedule an async job on the asyncio event loop. asyncio.run(scheduler()) The call to ``asyncio.create_task()`` submits the ``job_async`` coroutine to the asyncio event loop. -it may or may not run immediately, depending on the how busy the event loop is. +It may or may not run immediately, depending on the how busy the event loop is. Sleeping the right amount of time --------------------------------- @@ -29,6 +30,7 @@ Sleeping the right amount of time The ``scheduler()`` can be optimized by sleeping *exactly* [1]_ the amount of time until the next job is scheduled to run: .. code-block:: python + async def scheduler(): seconds = schedule.idle_seconds() while seconds is not None: @@ -36,12 +38,12 @@ The ``scheduler()`` can be optimized by sleeping *exactly* [1]_ the amount of ti # sleep exactly the right amount of time await asyncio.sleep(seconds) schedule.run_pending() + seconds = schedule.idle_seconds() -asyncio.run(scheduler()) -Keep in mind that if a new job is scheduled while sleeping, the new job's earliest run is whenever the sleep finishes. - + asyncio.run(scheduler()) +Keep in mind that if a new job is scheduled while sleeping, the new job's earliest run is whenever the sleep finishes. .. rubric:: Footnotes -.. [1] `asyncio.sleep` may sleep little more then expected, depending on loop implementation of loop and system load \ No newline at end of file +.. [1] `asyncio.sleep` may sleep little more then expected, depending on loop implementation of loop and system load. \ No newline at end of file diff --git a/docs/faq.rst b/docs/faq.rst index 48e4ef03..961c34c4 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -72,25 +72,7 @@ Does schedule support asyncio coroutines? ----------------------------------------- Schedule does not accept coroutines as jobs directly yet. However, using using ``asyncio.create_task`` you able to schedule async jobs. -See :doc:`this page ` for an example. - - -Can i use threading.Event.wait as means of scheduling and Terminating loop with loong sleep? ----------------------------------------------------------------------------------------- -Only in cases when you are running scheduling loop in separate thread -you also must make sure that you are not blocking main thread with joins/waiting on event etc. -This is because python will not handle any signals -However you can use ``asyncio.Event`` See :doc:`this page ` - -My long sleeping scheduler is not reacting on ctr+c ---------------------------------------------------- -In your code or other dependencies there is somewhere overridden signal handler -without also calling previous implementation that means KeyboardInterrupt is no longer thrown -so you will have to override their signal handler with your that exits loop and execute their implementation if needed. - -or you may be blocking main thread. - -See :doc:`this page `. +See :doc:`this page ` for an example. What happens when my computer hibernates during time.sleep/wait ---------------------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index 0f4c7c7d..f1195df4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,7 +16,7 @@ Python job scheduling for humans. Run Python functions (or any other callable) p - A simple to use API for scheduling jobs, made for humans. - In-process scheduler for periodic jobs. No extra processes needed! - Very lightweight and no external dependencies. -- Works with :doc:`asyncio coroutines` +- Works with :doc:`asyncio coroutines` - Excellent test coverage. - Tested on Python 3.6, 3.7, 3.8 and 3.9 From 5b045603bf2dc0dcb71f7df0d9538094229ace00 Mon Sep 17 00:00:00 2001 From: Sijmen Huizenga Date: Sat, 22 May 2021 15:52:13 +0200 Subject: [PATCH 5/5] Add link to more asyncio example in github --- docs/asyncio.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/asyncio.rst b/docs/asyncio.rst index 804cd37c..560397ed 100644 --- a/docs/asyncio.rst +++ b/docs/asyncio.rst @@ -44,6 +44,9 @@ The ``scheduler()`` can be optimized by sleeping *exactly* [1]_ the amount of ti Keep in mind that if a new job is scheduled while sleeping, the new job's earliest run is whenever the sleep finishes. +More examples +------------- +More advanced usage examples of schedule and asyncio can be found in `this thread `_. .. rubric:: Footnotes .. [1] `asyncio.sleep` may sleep little more then expected, depending on loop implementation of loop and system load. \ No newline at end of file