认识 python schedule 模块

简单使用

话不多说,安装步骤就此省略。 先来看最简单的一个使用:

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()

通过装饰器设置异常时取消任务。

参考

https://schedule.readthedocs.io/en/stable/

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值