在學(xué)習(xí)Flask的時(shí)候,《Flask Web開(kāi)發(fā)》這本書中有一個(gè)異步發(fā)送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
發(fā)送郵件總是提示錯(cuò)誤
RuntimeError: Working outside of application context.
后來(lái)查找資料才知道是傳遞current_app的問(wèn)題
current_app在 Flask是一個(gè)代理,如果你看 Flask源碼的話會(huì)發(fā)現(xiàn)其實(shí)它外部包裹的是這樣的:
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)
這個(gè) LocalProxy就不展開(kāi)講了,但是我可以告訴你這個(gè)LocalProxy的作用就是可以根據(jù)線程/協(xié)程返回對(duì)應(yīng)當(dāng)前協(xié)程/線程的對(duì)象,也就是說(shuō)
線程 A 往 LocalProxy 中塞入 A
線程 B 往 LocalProxy 中塞入 B
無(wú)論在是什么地方,線程 A 永遠(yuǎn)取到得是 A,線程 B 取到得永遠(yuǎn)是 B
這就是在 Flask中可以在代碼中直接使用 request、current_app這樣的變量的底層原因。
所以,因?yàn)檫@里開(kāi)了一個(gè)新線程,如果你不傳真實(shí)對(duì)象過(guò)去,那么你在線程里面使用 current_app將獲取不到對(duì)象,因?yàn)樗麤](méi)有 flask 上下文。
獲取真實(shí)對(duì)象Flask提供了一個(gè)方法:
_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函數(shù)后代碼如下:
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只是一個(gè)代理獲取而已,傳遞給其它子線程獲取到的依然是子線程的上下文
# 必須_get_current_object線獲取到原始對(duì)象再傳遞過(guò)去
app = current_app._get_current_object()
thr = Thread(target=send_async_email, args=(app, msg))
thr.start()
return thr
這樣就能異步發(fā)送郵件成功了