在学习Flask的时候,《Flask Web开发》这本书中有一个异步发送email的例子,
其中用到了线程
from . import mail,create_app
def send_async_email(app,msg):
with app.app_context():
mail.send(msg)
def send_email(to,subject,template,**kwargs):
msg = Message(current_app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
sender=current_app.config['FLASKY_MAIL_SENDER'], recipients=[to])
msg.body = render_template(template + '.txt', **kwargs)
msg.html = render_template(template + '.txt', **kwargs)
#with current_app.app_context():
# mail.send(msg)
thr = Thread(target=send_async_email, args=(current_app, msg))
thr.start()
return thr
发送邮件总是提示错误
RuntimeError: Working outside of application context.
后来查找资料才知道是传递current_app的问题
current_app在 Flask是一个代理,如果你看 Flask源码的话会发现其实它外部包裹的是这样的:
def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return top.app
...
current_app = LocalProxy(_find_app)
这个 LocalProxy就不展开讲了,但是我可以告诉你这个LocalProxy的作用就是可以根据线程/协程返回对应当前协程/线程的对象,也就是说
线程 A 往 LocalProxy 中塞入 A
线程 B 往 LocalProxy 中塞入 B
无论在是什么地方,线程 A 永远取到得是 A,线程 B 取到得永远是 B
这就是在 Flask中可以在代码中直接使用 request、current_app这样的变量的底层原因。
所以,因为这里开了一个新线程,如果你不传真实对象过去,那么你在线程里面使用 current_app将获取不到对象,因为他没有 flask 上下文。
获取真实对象Flask提供了一个方法:
_get_current_object()
官方文档是这样解释:
Return the current object. This is useful if you want the real object behind the proxy at a time for performance reasons or because you want to pass the object into a different context.
修改send_email函数后代码如下:
def send_email(to,subject,template,**kwargs):
msg = Message(current_app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
sender=current_app.config['FLASKY_MAIL_SENDER'], recipients=[to])
msg.body = render_template(template + '.txt', **kwargs)
msg.html = render_template(template + '.txt', **kwargs)
#with current_app.app_context():
# mail.send(msg)
#current_app只是一个代理获取而已,传递给其它子线程获取到的依然是子线程的上下文
# 必须_get_current_object线获取到原始对象再传递过去
app = current_app._get_current_object()
thr = Thread(target=send_async_email, args=(app, msg))
thr.start()
return thr
这样就能异步发送邮件成功了