Package: scheduled-futures
Scheduled Futuresconcurrent.futures extension with a ScheduledThreadPoolExecutor to handle delayed and periodic futures with reschedulingPreambleconcurrent.futuresis pretty awesome, but it does not handle periodic work which comes up a fair amount when creating applications.apscheduleris great, but trying to wait for jobs to complete involves a racy mess of callbacks.asynciois also good and can simply handle periodic work with aasyncio.sleepcall, but this is more trouble than it is worth when most of your codebase uses system calls / dlls / synchronous libraries you dont want to rewrite or coat withasyncio.to_threadcalls.This package was created to solve this problem. Please seeFeaturesbelow.Documentation is just what there is in this README and the code itself.Table of ContentsScheduled FuturesPreambleTable of ContentsInspirationFeaturesTechnologiesInstallDevelopmentExampleCodeOutputInspirationThis package was inspired bythis feature requestand some of the code there.FeaturesSchedule futures at a fixed delay using a thread pool executorRescheduling ofScheduledFutureinstances after they have startedScheduledThreadPoolExecutoris a subclass ofThreadPoolExecutor, so it can be used anywhereThreadPoolExecutorcan, such as for the asyncio default executorLogged warnings when aScheduledFuture'sruntime exceeded its period, indicating it may be scheduled to run too quicklyLogged warnings when aScheduledFutureis run later scheduled, indicating you may need more workers to keep upStatistics on the number of executions / exceptions / total runtime / average runtime for eachScheduledFutureTechnologiesPython >= 3.5Installpipinstallscheduled_futuresDevelopmentPull requests and new feature requests are welcome.ExampleCodeimporttimefromconcurrent.futuresimportwait,CancelledErrorimportloggingfromscheduled_futuresimportScheduledThreadPoolExecutorlogging.basicConfig()# to see warning messages from lib# which are pumped into a null handler by defaultdefwork():print(time.time(),'work!')defslow_work():time.sleep(0.25)print(time.time(),'slow work...huuh')withScheduledThreadPoolExecutor()asstpool:stpool.submit(work)# same interface as regular ThreadPoolExecutor still works# simple demo of scheduling a callable a little laterprint('\nDelayed future example')print(time.time(),'work scheduled')future=stpool.schedule(work,start=time.time()+0.5)time.sleep(1)# simple demo of scheduling a callable periodicallyprint('\nPeriodic future example')future=stpool.schedule(work,period=0.25)time.sleep(1)future.cancel()wait([future])# show log warning for each execution because the future# ran for longer than the period between runsprint('\nPeriod too short for long-running future example')future=stpool.schedule(slow_work,period=0.20)time.sleep(1)future.cancel()wait([future])withScheduledThreadPoolExecutor()asstpool:print('\nRescheduling work example')future=stpool.schedule(work,period=0.25)time.sleep(0.55)print('Rescheduling')stpool.reschedule(future,period=1)time.sleep(2)defslow_work2():time.sleep(0.15)print(time.time(),'slow work2...huuuuuuh')withScheduledThreadPoolExecutor(late_run_limit=0.1,max_workers=1)asstpool:# show log warning before some executions because there are not enough workers# to keep up with the execution scheduleprint('\nNot enough workers example')futures=[]for_inrange(3):futures.append(stpool.schedule(slow_work2,period=0.20))time.sleep(1)list(map(lambdaf:f.cancel(),futures))wait(futures)i=0defcancelled_work():globalii+=1ifi>=3:raiseCancelledError('Stop working, now!')print(time.time(),'ran without cancellation')withScheduledThreadPoolExecutor()asstpool:# cancel a periodic from inside a periodicprint('\nCancel from inside callable example')future=stpool.schedule(cancelled_work,period=0.25)try:future.result()exceptCancelledError:print('work cancelled!')Output1654566752.6947718 work!

Delayed future example
1654566752.6957695 work scheduled
1654566753.2106197 work!

Periodic future example
1654566753.700693 work!
1654566753.9519553 work!
1654566754.2048206 work!
1654566754.4566417 work!
1654566754.7094283 work!

Period too short for long-running future example
1654566754.963385 slow work...huuh
WARNING:scheduled_futures:Periodic scheduled future runtime exceeded period.
1654566755.4197686 slow work...huuh
WARNING:scheduled_futures:Periodic scheduled future runtime exceeded period.
1654566755.8865035 slow work...huuh
WARNING:scheduled_futures:Periodic scheduled future runtime exceeded period.

Rescheduling work example
1654566755.8884244 work!
1654566756.1499033 work!
1654566756.4140623 work!
Rescheduling
1654566756.4449573 work!
1654566757.454106 work!
1654566758.462207 work!

Not enough workers example
1654566758.6189985 slow work2...huuuuuuh
WARNING:scheduled_futures:Late to run scheduled future.
1654566758.7743778 slow work2...huuuuuuh
WARNING:scheduled_futures:Late to run scheduled future.
1654566758.932357 slow work2...huuuuuuh
WARNING:scheduled_futures:Late to run scheduled future.
1654566759.0854318 slow work2...huuuuuuh
WARNING:scheduled_futures:Late to run scheduled future.
1654566759.2414527 slow work2...huuuuuuh
WARNING:scheduled_futures:Late to run scheduled future.
1654566759.3976445 slow work2...huuuuuuh
WARNING:scheduled_futures:Late to run scheduled future.
1654566759.5528805 slow work2...huuuuuuh

Cancel from inside callable example
1654566759.554882 ran without cancellation
1654566759.8188088 ran without cancellation
work cancelled!