Python3关于current_app传递给子线程

在学习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

这样就能异步发送邮件成功了

 

 

转自:https://www.jianshu.com/p/acaf646f537c

 

 

 

 

 

posted @ 2019-07-03 17:04  aidenzdly  阅读(818)  评论(1编辑  收藏  举报