schedPython标准库模块,提供了一个用于事件调度的类。

一、scheduler类定义

class sched.scheduler(timefunc=time.monotonic, delayfunc=time.sleep) 
scheduler类为事件调度定义了一套通用接口。它需要传入两个函数:1)timefunc是一个没有参数的callable,并且返回一个一个数字(表示“时间”,任意单位)。如果time.monotonic不是可用的,则默认为time.time。2)delayfunc是带有一个参数的callable,与timefunc的输出兼容,用于延迟一段时间。在多线程应用中,为了给其它线程执行的机会,在每个事件执行后,delayfunc也将使用参数0调用。
从3.3版本开始,scheduler是线程安全的。
下面是一个例子:

#coding=utf8
import sched, time
import threading

s = sched.scheduler(time.time, time.sleep)

def print_time(a='default'):
    print("From print_time", time.time(), a)

def print_some_times():
    print(time.time())
    s.enter(10, 1, print_time,())
    s.enter(5, 2, print_time, argument=('positional',))
    s.enter(5, 1, print_time, kwargs={'a': 'keyword'})
    s.run()
    print(time.time())

#scheduler.run 3.3版本前会阻塞主线程,因此独立创建线程执行定时任务
thread_time = threading.Thread(target = print_some_times)
thread_time.start()

》》》
930343690.257
From print_time 930343695.274 positional
From print_time 930343695.275 keyword
From print_time 930343700.273 default
930343700.276

Scheduler对象

Scheduler实例有以下方法和属性:

scheduler.enterabs(time, priority, action, argument=(), kwargs={})
调度一个新事件。time参数应该是一个数字类型,与构造器传入的timefunc函数的返回值兼容。指定到同一个时间的事件调度将按他们的优先级顺序依次执行。
执行时间即为执行action(*argument, **kwargs)。argument是一个序列,按照action的参数顺序排列;kwargs是一个map,使用key对应action的参数。
返回值是一个事件,可以被用于事件的取消(看cancel())。

scheduler.enter(delay, priority, action, argument=(), kwargs={})
在延迟delay时间后调度一个事件。除了使用相对时间,其它的参数和返回值和enterabs是相同的。

scheduler.cancel(event)
从队列中移除事件。如果事件不在当前队列中,该方法抛出ValueError。

scheduler.empty()
如果队列是空的,则返回True。

scheduler.run(blocking=True)
运行所有的事件,这个方法将等待(使用传递给构造器的delayfunc()函数)下一个事件的时间到达,然后执行它,直到所有的事件都被执行。
如果blocking为false,则不阻塞等待,立即调度溢出时间的那些时间(如果存在),然后返回在调度器中的下次调度的需要等待的时间,例如:

import sched, time
s = sched.scheduler(time.time, time.sleep)
def print_time(a='default'):
    print("From print_time", time.time(), a)
def print_some_times():
    print(time.time())
    s.enter(10, 1, print_time)
    s.enter(5, 2, print_time, argument=('positional',))
    s.enter(5, 1, print_time, kwargs={'a': 'keyword'})
    print("Next : ",s.run(False))
    print(time.time())

第一次调用:

>>> print_some_times()
1435115632.601069
Next :  5.0
1435115632.656073

Next表示下一个事件将在5秒后执行。第二次超过10秒后调用:

>>> print_some_times()
1435115665.549954
From print_time 1435115665.596957 keyword
From print_time 1435115665.607957 positional
From print_time 1435115665.618958 default
Next :  4.966997861862183
1435115665.635959

这时事件已经全部达到执行时间点,所以全部立即执行。
action后者delayfunc能抛出一个异常,这时,调度器将保持一致并传递该异常。如果异常被action抛出,以后该事件将不会再被执行。
如果一个事件执行的结束时间超过了下一个事件的执行时间,调度器将忽略下一个事件。没有事件会被丢弃。

scheduler.queue
将要执行的事件列表,列表只读,事件按照将要执行的顺序排列。每个事件存储为一个元组,包含:time、priority、action、argument和kwargs。 

二、Timer类基本介绍

这个类表示一个动作应该在一个特定的时间之后运行 — 也就是一个计时器。Timer是Thread的子类, 因此也可以使用函数创建自定义线程。

class threading.Timer(interval, function, args=[], kwargs={}) 
创建一个timer,在interval秒过去之后,它将以参数args和关键字参数kwargs运行function 。

相比与Thread,它多了一个cancel()方法,能在对象还没执行完成的时候停止这个对象。

#/usr/bin/python3
# -*- utf-8 -*-
#学习版本3.5.2
 
import threading
import time
 
def test():
    print("start test")
    print("stop test")
 
if __name__ == "__main__":
    t1 = threading.Timer(2, test)
    t2 = threading.Timer(2, test)
 
    t1.start()
    t1.cancel()
    t2.start()
 
    print("main")

运行结果

main
start test
stop test

用Timer做每隔几秒执行一次的定时器的话需要函数里面有一个Timer定时任务,循环调用,此时cancel就不能停止定时任务了,因为,每次定时任务的线程id都在变化,执行完的定时任务的线程已经结束

import threading
import time

def hello(name):
    print "hello %s\n" % name

    global timer
    timer = threading.Timer(2.0, hello, ["Hawk"])
    timer.start()

if __name__ == "__main__":
    timer = threading.Timer(2.0, hello, ["Hawk"])
    timer.start()

 例定时到每天七点启动

import datetime

def get_picture(dict):
    start_hour = "07:00"  
    start_hour = int(start_time.split(':')[0])
    start_minute = int(start_time.split(':')[1])
    cur_time = datetime.datetime.now()
    des_time = cur_time.replace(hour=start_hour, minute=start_minute, second=0, microsecond=0)
    delta = cur_time - des_time
    SECONDS_PER_DAY = 24 * 60 * 60
    skipSeconds = SECONDS_PER_DAY - delta.total_seconds() + 1      #多预留出来1s
    timer = threading.Timer(skipSeconds, get_picture, (mac_dict,))
    timer.start()

 三、apscheduler

安装

我一开始在python2.6中安装的,但是安装有错误,估计是支持不好,在python2.7中是可以的

pip install apscheduler -i https://pypi.douban.com/simple

安装过程中会额外安装一个pytz的库,是关于timezone的

基础概念 参考 http://debugo.com/apscheduler/

触发器(trigger)包含调度逻辑,每一个作业有它自己的触发器,用于决定接下来哪一个作业会运行。除了他们自己初始配置意外,触发器完全是无状态的。
作业存储(job store)存储被调度的作业,默认的作业存储是简单地把作业保存在内存中,其他的作业存储是将作业保存在数据库中。一个作业的数据讲在保存在持久化作业存储时被序列化,并在加载时被反序列化。调度器不能分享同一个作业存储。
执行器(executor)处理作业的运行,他们通常通过在作业中提交制定的可调用对象到一个线程或者进城池来进行。当作业完成时,执行器将会通知调度器。
调度器(scheduler)是其他的组成部分。你通常在应用只有一个调度器,应用的开发者通常不会直接处理作业存储、调度器和触发器,相反,调度器提供了处理这些的合适的接口。配置作业存储和执行器可以在调度器中完成,例如添加、修改和移除作业。
你需要选择合适的调度器,这取决于你的应用环境和你使用APScheduler的目的。通常最常用的两个:
– BlockingScheduler: 当调度器是你应用中唯一要运行的东西时使用。
– BackgroundScheduler: 当你不运行任何其他框架时使用,并希望调度器在你应用的后台执行。

最主要用的就是scheduler与job store

作业存储

一般存储分为内存存储和持久化的存储,推荐使用持久化的存储,这样一旦主机挂了或者重启了,这样只要重新运行脚本就可以接着运行了,我这里使用的是mongodb来存储。

调度器

任务的执行调度工作由其来完成,主要用到的有BlockingScheduler(阻塞的),BackgroundScheduler(非阻塞的)

一个简单的例子,每隔5s钟输出‘hello world’,每天的13点50分输出‘i m blocking task’

#coding:utf-8

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.schedulers.blocking import BlockingScheduler
import time
from datetime import datetime
from pymongo import MongoClient

mongoDBhost = 'vps.yangyanxing.com' #mongodb 服务器
mongoDBport = '29017' # 端口号
mongoDBuser = 'yangyanxing' # 用户名
mongoDBpwd = 'pwd' # 密码

mongoclient = MongoClient(host=['%s:%s'%(mongoDBhost,mongoDBport)])
mongoclient.admin.authenticate(mongoDBuser,mongoDBpwd)

dbjob = mongoclient.mac.jobs  # mongodb所用到的collection,这里是BackgroundScheduler的
dbjob_b = mongoclient.mac.jobs_block # mongodb所用到的collection,这里是blockingScheduler的

job_defaults = {
    'coalesce': False,
    'max_instances': 3,
    'misfire_grace_time': 30
}

def timetest():
    print 'hello world'

def timetestblock():
    print 'i m blocking task'

if __name__ == '__main__':
    scheduler = BackgroundScheduler(job_defaults=job_defaults)
    scheduler_b = BlockingScheduler(job_defaults=job_defaults)
    scheduler.add_jobstore('mongodb', client=dbjob)
    scheduler_b.add_jobstore('mongodb', client=dbjob_b)
    scheduler.add_job(timetest, 'interval', seconds=5)
    scheduler_b.add_job(timetestblock, 'cron', minute=50, hour=13, start_date=datetime.now())
    scheduler.start()
    scheduler_b.start()

如果运行脚本发现报错

python schedule 假死_scheduler

有的环境是不报这个错误的,如果报的话就手动指定一个时区,指定一个pytz库中定义了的时区,可以在site-packages中查看一下,具体原因没有细追

timez = pytz.timezone('Asia/Shanghai')
# 初始化scheduler的时候加上timezone参数
scheduler = BackgroundScheduler(job_defaults=job_defaults,timezone=timez)
scheduler_b = BlockingScheduler(job_defaults=job_defaults,timezone=timez)

最终代码

#coding:utf-8

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.schedulers.blocking import BlockingScheduler
import time
from datetime import datetime
from pymongo import MongoClient
import pytz

'''
两个问题
1. 对于 interval,比如说每5秒钟进行一次的任务,当该脚本被停了,重新运行该脚本的时候,已经过了nexttime了,
   程序是怎么操作的
2. 报pytz.exceptions.UnknownTimeZoneError: u'Can not find timezone China Standard time' 的问题
'''

timez = pytz.timezone('Asia/Shanghai')
# 当有不识别的timezone的时候,初始化的时候可以加上timezone,最好也要加上,否则时间日期不对应会出问题
mongoDBhost = 'vps.yangyanxing.com'
mongoDBport = '29017'
mongoDBuser = 'yangyanxing'
mongoDBpwd = 'pwd'



mongoclient = MongoClient(host=['%s:%s'%(mongoDBhost,mongoDBport)])
mongoclient.admin.authenticate(mongoDBuser,mongoDBpwd)

dbjob = mongoclient.mac.jobs
dbjob_b = mongoclient.mac.jobs_block

job_defaults = {
    'coalesce': False,
    'max_instances': 3,
    'misfire_grace_time': 30
}

def timetest():
    print time.strftime('%Y%m%d--%H:%M:%S',time.localtime(time.time())),'hello world'

def timetestblock():
    print 'i m blocking task'


if __name__ == '__main__':
    scheduler = BackgroundScheduler(timezone=timez)
    scheduler_b = BlockingScheduler(timezone=timez)
    scheduler.add_jobstore('mongodb', client=dbjob)
    scheduler_b.add_jobstore('mongodb', client=dbjob_b)
    scheduler.add_job(timetest, 'interval', seconds=5)
    scheduler_b.add_job(timetestblock, 'cron', minute=50, hour=13, start_date=datetime.now())
    scheduler.start()
    scheduler_b.start()

几个问题

1、运行的时候会有一些误差,由于我这个mongodb在国外的VPS上,所以在操作的时候就有一些延迟,正常如果很快的话误差不会很大
2、关于timezone,如果有报错的话则要手工的指定,在中国境内可以定义为’Asia/Shanghai’
3、添加作业的时候,类型可以为cron,这个定义和linux中的crontab格式,比较灵活,而且它本身就可以定义第周几进行,第几个星期几等,推荐使用
4、添加作业的时候也可以使用装饰器

@scheduler_b.scheduled_job('cron',id='timetest_b',minute=50, hour=13)
def timetestblock():
    print 'i m blocking task'