写应用的时候遇到这样的一个问题,定义的celery task中无法使用current_app,并且有时候会出现pop wrong app context。

这个问题是因为celery中执行task的work和正在运行的web应用app之间的进程是不一样的,虽然是在一个项目里面,但是app的进程数据是不可能什么都不做就和celery的worker进程进行共享的。也就是说,在worker执行task的时候,根本就没有app这一系列东西,所以上下文栈就是空的,因此就不能使用app,以及app.logger了。

知道这一点之后,就在celery的task调用的时候加上了with app.app_context():,但是有时候task执行出错的时候,或者运行完成的时候就会出现,AssertionError('Popped wrong app context. (<flask.ctx.AppContext object at 0x7f4e1ed56d30> instead of <flask.ctx.AppContext object at 0x7f4e1f02c7f0>)',),这个问题后来找到可能的原因是,多个celery worker执行的时候会创建多个app实例,并且对于一个进程的上下文栈进行了多次的上下文进栈,在发生错误的时候,就会导致出栈的上下文混乱。

最后的解决方案是,给celery的Task类做猴子补丁,在调用task的时候,只使用唯一的app实例(对于每个进程来讲,上下文栈是不同的)进行上下文切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def make_celery(app):
celery = Celery('backend', broker='redis://%s:%d/%d' % (REDIS_HOST, REDIS_PORT, REDIS_DB),
backend='redis://%s:%d/%d' % (REDIS_HOST, REDIS_PORT, REDIS_DB))
TaskBase = celery.Task
class ContextTask(TaskBase):
abstract = True
def __call__(self, *args, **kwargs):
with app.app_context():
return TaskBase.__call__(self, *args, **kwargs)
celery.Task = ContextTask
return celery

这样就可以开心的在celery的task里面使用current_app