简单使用
话不多说,安装步骤就此省略。 先来看最简单的一个使用:
import pprint
import schedule
import time
def job():
print("I'm working...")
def job1(name):
print(name)
def main():
"""模块简单使用"""
schedule.every(10).minutes.do(job) # 每10分钟执行一次
schedule.every().hour.do(job) # 每小时执行一次
schedule.every().day.at("10:30").do(job) # 每天10:30执行一次
schedule.every().monday.do(job) # 每周星期一执行一次
schedule.every().wednesday.at("13:15").do(job) # 每周星期三执行一次
schedule.every().wednesday.at("13:15").do(job1, 'waiwen') # 传入参数
while True:
print("当前调度系统中的任务列表是:\n{}".format(pprint.pformat(schedule.jobs)))
schedule.run_pending()
time.sleep(10)
if __name__ == "__main__":
main()
并行任务
我设置了两个任务,一个耗时大约 100 s(task1), 另外一个耗时大约 10 s(task2)。两个任务均是每 10s 运行一次。
这样就出现了一个问题: 在任务 1 没运行完成时, 任务 2 需要进行开启。
import pprint
import time
import schedule
def task1():
print("停留 100 s 的任务 1")
time.sleep(100)
print("任务 1 结束")
def task2():
print("停留 10 s 的任务 2")
time.sleep(10)
print("任务 2 结束")
def main():
schedule.every(10).seconds.do(task1)
schedule.every(10).seconds.do(task2)
while True:
print("当前调度系统中的任务列表是:\n{}".format(pprint.pformat(schedule.jobs)))
schedule.run_pending()
time.sleep(1)
if __name__ == "__main__":
"""schedule 非并行"""
main()
运行结果:
可以看到, task1 和 task2 是串行执行的。
看一下源码:
schedule 对象在 run_pending 的时候,按照下一次运行时间的先后将任务列表中的 job 对象进行排序。 根据任务下一次执行的时间判断当前任务是否应该被执行。
被选定的 job 开始被执行:
综上,schedule 只是在一个事件循环中,根据触发条件 shoule_run 做了一个简单的调度。并不具备并行执行的功能。 默认任务运行时间过长就会阻塞主线程。
使用多线程
import pprint
import threading
import time
import schedule
def task1():
print("停留 100 s 的任务 1")
time.sleep(100)
print("I'm running on thread %s" % threading.current_thread())
print("任务 1 结束")
def task2():
print("停留 10 s 的任务 2")
time.sleep(10)
print("I'm running on thread %s" % threading.current_thread())
print("任务 2 结束")
def run_threaded(job_func):
job_thread = threading.Thread(target=job_func)
job_thread.start()
def main():
schedule.every(10).seconds.do(run_threaded, task1)
schedule.every(10).seconds.do(run_threaded, task2)
while 1:
thread_num = len(threading.enumerate())
print("主线程:线程数量是%d" % thread_num)
# print(pprint.pformat(schedule.jobs))
schedule.run_pending()
time.sleep(1)
if __name__ == "__main__":
main()
运行结果:
线程数量会随着任务数的增加而叠加,不会阻塞。也就不会因为超过时间而丢失任务。
队列
将需要执行的任务放入队列中,也是一种不丢失任务的选择:
import queue
import time
import threading
import schedule
def task1():
print("停留 100 s 的任务 1")
time.sleep(100)
print("任务 1 结束")
def task2():
print("停留 10 s 的任务 2")
time.sleep(10)
print("任务 2 结束")
def worker_main():
while 1:
job_func = jobqueue.get()
job_func()
'''
如果线程里每从队列里取一次,但没有执行task_done(),则join无法判断队列到底有没有结束,在最后执行个join()是等不到结果的,会一直挂起。
可以理解为,每task_done一次 就从队列里删掉一个元素,这样在最后join的时候根据队列长度是否为零来判断队列是否结束,从而执行主线程。
'''
jobqueue.task_done()
if __name__ == "__main__":
jobqueue = queue.Queue()
schedule.every(10).seconds.do(jobqueue.put, task1)
schedule.every(10).seconds.do(jobqueue.put, task2)
worker_thread = threading.Thread(target=worker_main)
worker_thread.start()
while 1:
print("队列中的任务数量是: ", jobqueue.qsize())
schedule.run_pending()
time.sleep(1)
调度只执行一次的任务
import time
import schedule
def job_that_executes_once():
# Do some work ...
print("我是只运行一次的任务.. ")
return schedule.CancelJob
def main():
schedule.every().day.at('13:35').do(job_that_executes_once)
while True:
print(schedule.jobs)
schedule.run_pending()
time.sleep(10)
if __name__ == "__main__":
main()
取消任务
import pprint
import time
import schedule
def greet(name):
print('Hello {}'.format(name))
def main():
job1 = schedule.every().day.do(greet, 'Andrea')
job1.tag('daily-tasks', 'friend')
job2 = schedule.every().hour.do(greet, 'John')
job2.tag('hourly-tasks', 'friend')
job3 = schedule.every().hour.do(greet, 'Monica')
job3.tag('hourly-tasks', 'customer')
job4 = schedule.every().day.do(greet, 'Derek')
job4.tag('daily-tasks', 'guest')
for job in (job1, job2, job3, job4):
print(job.tags)
# schedule.every().day.do(greet, 'Andrea').tag('daily-tasks', 'friend')
# schedule.every().hour.do(greet, 'John').tag('hourly-tasks', 'friend')
# schedule.every().hour.do(greet, 'Monica').tag('hourly-tasks', 'customer')
# schedule.every().day.do(greet, 'Derek').tag('daily-tasks', 'guest')
# schedule.clear('daily-tasks')
schedule.clear('friend')
while True:
schedule.run_pending()
print(pprint.pformat(schedule.jobs))
time.sleep(10)
if __name__ == "__main__":
main()
通过 schedule 源码,可以看到我们可以将 Job 对象打上多个便签进行管理。
随机选择时间运行
源码比较喜欢使用链式调用 …
to 的功能就是赋予一个 latest 属性, 然后在 interval 和 to 之间随机一个值去作为运行间隔。
import time
import schedule
_t = None
def my_job():
global _t
_now = time.time()
if _t:
print("interval: ", _now - _t)
else:
print("first: ")
_t = _now
def main():
schedule.every(5).to(10).seconds.do(my_job)
while True:
schedule.run_pending()
time.sleep(1)
if __name__ == "__main__":
main()
捕获任务异常
import functools
import time
import traceback
import schedule
def catch_exceptions(cancel_on_failure=False):
def catch_exceptions_decorator(job_func):
@functools.wraps(job_func)
def wrapper(*args, **kwargs):
try:
return job_func(*args, **kwargs)
except:
print(traceback.format_exc())
# sentry.captureException(exc_info=True)
if cancel_on_failure:
print("异常 任务结束: {}".format(schedule.CancelJob))
schedule.cancel_job(job_func)
return schedule.CancelJob
return wrapper
return catch_exceptions_decorator
# def catch_exceptions(job_func, cancel_on_failure=False):
# @functools.wraps(job_func)
# def wrapper(*args, **kwargs):
# try:
# return job_func(*args, **kwargs)
# except:
# import traceback
# print(traceback.format_exc())
# if cancel_on_failure:
# return schedule.CancelJob
# return wrapper
@catch_exceptions(cancel_on_failure=True)
def bad_task():
return 1 / 0
def main():
schedule.every(5).seconds.do(bad_task)
while True:
print("jobs: ", schedule.jobs)
schedule.run_pending()
time.sleep(1)
if __name__ == "__main__":
main()
通过装饰器设置异常时取消任务。