标准的异常处理是这样的
try:
print('hello')
raise Exception()
print('!!!')
except:
print('world')
print('???')
这段代码会打印出???而不会打印出!!!,因为异常会中断当前流程,跳转到except部分去继续执行。但是有的时候我们希望的这样的行为:
try:
print('hello')
print(Scheduler.interrupt())
print('!!!')
except ProcessInterrupt as pi:
pi.resume('world')
print('???')
这段代码打印出!!!而不是???,因为resume的时候把执行重新跳转回interrupt的地方了。这种行为类似vba里的on error resume next(https://msdn.microsoft.com/en-us/library/5hsw66as.aspx)。
如何实现的?其实原理上很简单。interrupt的时候把当前协程的状态保存起来(pickle.dumps),如果决定要resume,就把协程interrupt的时刻的状态重新恢复(pickle.loads)然后从那个点继续执行。
完整的代码(需要pypy或者stackless python):
import greenlet
import cPickle as pickle
import traceback
import threading
import functools
class ProcessInterrupt(Exception):
def __init__(self, interruption_point, pi_args):
self.interruption_point = interruption_point
self.stacktrace = traceback.extract_stack()
self.pi_args = pi_args
def resume(self, resume_with=None):
Scheduler.resume(self.interruption_point, resume_with)
def __repr__(self):
return '>>>ProcessInterrupt>>>%s' % repr(self.stacktrace)
def __str__(self):
return repr(self)
def __unicode__(self):
return repr(self)
class Scheduler(object):
current = threading.local()
def __init__(self):
if getattr(self.current, 'instance', None):
raise Exception('can not have two scheduler in one thread')
self.scheduler_greenlet = greenlet.getcurrent()
self.current.instance = self
def __call__(self, action, action_args):
next = action, action_args
while next:
action, action_args = next
if 'init' == action:
next = action_args['init_greenlet'].switch()
elif 'interrupt' == action:
interruption_point = pickle.dumps(action_args['switched_from'])
should_resume, resume_with = False, None
next = action_args['switched_from'].switch(
should_resume, resume_with, interruption_point)
elif 'resume' == action:
should_resume, resume_with, interruption_point = True, action_args['resume_with'], action_args[
'interruption_point']
next = pickle.loads(action_args['interruption_point']).switch(
should_resume, resume_with, interruption_point)
else:
raise NotImplementedError('unknown action: %s' % action)
@classmethod
def start(cls, init_func, *args, **kwargs):
scheduler = Scheduler()
init_greenlet = greenlet.greenlet(functools.partial(init_func, *args, **kwargs))
scheduler('init', {
'init_greenlet': init_greenlet,
})
@classmethod
def interrupt(cls, pi_args=None):
should_resume, resume_with, interruption_point = cls.switch_to_scheduler('interrupt', {
'switched_from': greenlet.getcurrent()
})
if should_resume:
return resume_with
else:
pi = ProcessInterrupt(interruption_point, pi_args)
raise pi
@classmethod
def resume(cls, interruption_point, resume_with=None):
cls.switch_to_scheduler('resume', {
'interruption_point': interruption_point,
'resume_with': resume_with
})
@classmethod
def switch_to_scheduler(cls, *args, **kwargs):
return cls.current.instance.scheduler_greenlet.switch(*args, **kwargs)
if '__main__' == __name__:
def init():
try:
print('hello')
print(Scheduler.interrupt())
print('!!!')
except ProcessInterrupt as pi:
pi.resume('world')
print('???')
try:
print('hello')
raise Exception()
print('!!!')
except:
print('world')
print('???')
Scheduler.start(init)