关键字:Flask, Redis, RabbitMQ, Celery, Broker, Backend
前言
在后端服务器有时候需要处理耗时较长的任务,例如发送电子邮件,在处理这些任务时,这个线程就处于阻塞状态而无法处理新的请求,服务器性能就大大降低,可以将这些耗时较长的任务交给任务队列来处理,处理完成后告诉我们结果就可以了,这样服务器处理请求的能力得到了极大的提高,本文将介绍Flask如何使用Celery来处理耗时任务(Windows环境下,以后有机会再发Linux下的)。
1、Celery工作原理
Celery 是一个异步任务队列。通俗的讲它就是我们的助理,当我们要出差的时候,它会帮我们安排好车辆、订好机票、预约酒店等等。把Flask看成老板,Celery就是它的助理,帮助老板处理琐碎的事情,这样老板就有更多的时间处理其他重要的事情。
消息队列(message queue)作为中间件(Broker),一般可选RabbitMQ或者Redis,通俗的讲它就是我们和助理沟通的工具,就像一个日程表,我们将需要助理处理的事情记录在上面,助理就会看到这些事情然后去处理,最后把处理的结果记录在日程表上,当我们看到处理结果后,日程表上的这个事项就划掉了,有时候我们需要保存这些处理结果,所以为助理专门准备了一个记录处理结果(Backend)的小本本,这个小本本通常使用Redis。
整个系统构成经典的生产者消费者模型。生产者和消费者是相对于消息队列的,老板(相当于这里的Flask App)往消息队列添加任务,所以老板是生产者,助理(Celery的Worker)从消息队列提取任务,所以是消费者。
使用Celery主要有三个好处:
1. 应用解耦
消息是与平台无关的,Flask只需要把需求告诉消息队列即可,由谁来完成并不需要关心,当访问量增加时对Flask不会造成明显的冲击。
2. 异步通信
缩短了请求等待的时间,提高了页面的吞吐量,尤其是瞬间高流量时,消息队列能够有效缓解访问压力。
3. 高可靠性
消息队列冗余机制确保了消息最终都会被处理,不会造成数据或操作丢失的情况。
2、准备工作
要使用Celery,那么首先就要确定它的中间件和结果缓冲区是什么,然后安装并配置好Celery的worker就算是完成了初步的准备工作。
① 中间件(Broker)
可以使用RabbitMQ,这也是一般的最佳实践,下载地址如下:
也可以直接使用Redis作为中间件,下载地址如下:
② 结果缓冲区(Backend)
这里为了简单起见,两者都直接采用Redis。
③ 安装并配置Celery
直接使用命令安装Celery到Python环境:
pip install celery
在Flask配置文件中加入Celery的配置信息,Project/config.py
class Config():
# other config info
CELERY_BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/1'
在Flask项目模块工厂函数文件中添加Celery的工厂函数,Project/webapp/init.py:
from flaskimport Flask
from celeryimport Celery
# 创建Flask App的工厂函数
def create_app(config_name) ->'app':
app =Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
from .mainimport mainas main_blueprint
app.register_blueprint(main_blueprint)
return app
# 创建celery的工厂函数
def make_celery(app):
celery = Celery(
app.import_name,
backend=app.config['CELERY_RESULT_BACKEND'],
broker=app.config['CELERY_BROKER_URL']
)
# celery.conf.update(app.config)
class ContextTask(celery.Task):
def __call__(self, *args, **kwargs):
"""Will be execute when create the instance object of ContextTask"""
with app.app_context():
return self.run(*args, **kwargs)
# 将app_context 包含在celery.Task中,这样让其他的Flask扩展也能正常使用
celery.Task = ContextTask
return celery
Celery 的进程必须在 Flask app 的上下文中运行, 这样 Celery 才能够跟其他的 Flask 扩展协同工作。所以必须将 flask_celery 对象注册到 app 对象中, 并且每创建一个 Celery 进程都需要创建一个新的 Flask app 对象, 这里我们使用工厂模式来创建 celery 对象。
这一点是非常重要的,实际上 Flask application 和Celery application 是两个不同的进程,在 Celery 没有加入 Flask 上下文的情况下,Celery 的程序逻辑就不能轻易的访问 Flask 相关资源,比如不能加载 Flask的环境配置信息,无法通过 Flask 来访问数据库,不能使用 Flask 的扩展功能等。如果想做到这些,Celery 都需要自己再实现一套相同的逻辑,这样做显然是没有必要的。所以Flask application 原生支持将自己的 Context 嵌入到别的 application 中,当然有些情况也需要相应扩展的辅助。
注意:这里将 celery.conf.update(app.config) 给注释起来了,因为会报错,这个错误还没有弄清楚是什么原因。
3、创建Worker的任务函数
在main文件夹下新建一个tasks.py文件,来存放需要异步执行的耗时任务,首先通过工厂函数make_celery创建Celery实例,后台任务其实就是一个函数,把要执行的动作放入这个函数中,然后使用celery.task这个装饰器来装饰就可以了。
这里只有一个任务,任务的装饰器包含了bind参数,表示Celery发送一个self参数到我们的任务,这个参数主要用来使用update_state方法更新当前任务执行状态,update_state方法两个参数,1个state表示当前状态,meta是相关的数据,其实就是一个字典。
Project/webapp/main/tasks.py:
import random
import time
from .. import make_celery, create_app
celery = make_celery(create_app('default'))
@celery.task()
def log(msg):
print(msg)
return msg
# 通过celery.task装饰耗时任务函数,bind为True会传入self给被装饰的函数,用于记录和更新任务状态
@celery.task(bind=True)
def long_task(self):
# 耗时任务逻辑
verb = ['Starting up', 'Booting', 'Repairing', 'Loading', 'Checking']
adjecetive = ['master', 'radiant', 'silent', 'harmonic', 'fast']
noun = ['solar array', 'particle reshaper', 'cosmic ray', 'orbiter', 'bit']
message = ''
total = random.randint(10, 50)
# 每次获取一次词汇,就通过 self.update_state () 更新 Celery 任务的状态,Celery 包含一些
# 内置状态,如 SUCCESS、STARTED 等等,这里使用了自定义状态「PROGRESS」,除了状态外,还将本
# 次循环的一些信息通过 meta 参数 (元数据) 以字典的形式存储起来。有了这些数据,前端就可以显示进度条了。
for i in range(total):
if not message or random.random() < 0.25:
# 随机取一些信息
message = '{0} {1} {2}...'.format(random.choice(verb), random.choice(adjecetive), random.choice(noun))
print(message)
# 更新Celery任务状态
self.update_state(state='PROGRESS', meta={'current': i, 'total': total, 'status': message})
time.sleep(0.5) # 每次loop延时0.5秒,为了表示这是耗时任务
# 返回字典
return {'current': 100, 'total': 100, 'status': 'Task Completed!', 'result': u'完成'}
4、在视图中产生消息
通常在视图函数中,处理业务逻辑时调用后台任务,有两种方法,一种是调用任务的delay方法,直接传入参数即可,一种是调用入伍的apply_async方法,任务的参数通过args传递,countdown是定时任务,表示多少秒以后执行。
通常我们将耗时较长的任务编写成一个函数,然后使用celery.task装饰器将其转化成一个任务对象,最后在视图函数中需要使用的时候调用它的delay或者apply_async方法来执行。
Project/webapp/main/view.py:
from flask import render_template, url_for
import time
import random
from . import main
from .tasks import long_task, log
# Server 路由
@main.route('/', methods=['GET', 'POST'])
@main.route('/index', methods=['GET', 'POST'])
def index():
log.delay("helo, world!")
log.apply_async(args=["this is a message from main.index"], countdown=60)
return render_template('index.html')
5、前端实时显示耗时过程执行状态的应用范例
接下来我们就实现一个在前端实时显示后端程序执行状态的进度条应用范例,废话不多说直接上代码。
① 后端产生消息
Project/webapp/main/view.py:
# 耗时任务视图接口
@main.route('/longtask', methods=['GET'])
def longtask():
# 异步调用
task = long_task.apply_async()
# 返回202和Location头
# 返回的状态码为 202,202 通常表示一个请求正在进行中,然后还在返回数据包的包头 (Header) 中添加了 Location 头信息
# 前端可以通过读取数据包中 Header 中的 Location 的信息来获取任务 id 对应的完整 url
# 前端有了任务 id 对应的 url 后,还需要提供一个接口给前端,让前端可以通过任务 id 去获取当前时刻任务的具体状态
return jsonify({}), 202, {'Location': url_for('main.taskstatus', task_id=task.id)}
这个视图函数用来启动后台任务,返回结果中给出了任务状态的地址,通过task.id将当前任务传递到任务状态的视图函数,这样状态的视图函数就能找到这个任务,202状态码表示正在执行。
② 请求任务状态
Project/webapp/main/view.py:
@main.route('/status/<task_id>', methods=['GET'])
def taskstatus(task_id):
# 为了可以获得任务对象中的信息,使用任务 id 初始化 AsyncResult 类,获得任务对象,然后就可以从任务对象中获得当前任务的信息
task = long_task.AsyncResult(task_id)
# 如果任务在 PENDING 状态,表示该任务还没有开始,在这种状态下,任务中是没有什么信息的,这里人为的返回一些数据
# 如果任务执行失败,就返回 task.info 中包含的异常信息,此外就是正常执行了,正常执行可以通 task.info 获得任务中具体的信息
if task.state == 'PENDING': # 等待中
response = {
'state': task.state,
'current': 0,
'total': 1,
'status': 'Pending...'
}
elif task.state != 'FAILURE': # 没有失败
response = {
'state': task.state,
# meta中的数据,通过task.info.get()可以获得
'current': task.info.get('current', 0), # 当前循环进度
'total': task.info.get('total', 1), # 总循环进度
'status': task.info.get('status', '')
}
if 'result' in task.info:
response['result'] = task.info['result']
else:
# 后端执行任务出现了一些问题
response = {
'state': task.state,
'current': 1,
'total': 1,
'status': str(task.info) # 错误的具体描述
}
# 该方法会返回一个 JSON,其中包含了任务状态以及 meta 中指定的信息,前端可以利用这些信息构建一个进度条
return jsonify(response)
这个就是用来请求任务状态的路由,首先使用task_id找到正在执行的任务。通过task.state可以获取到任务的状态,PENDING表示任务还未开始,通过task.info.get()方法得到前面self.update_state中meta参数的内容。
后端的代码就已经完成了,然后就是前端来获取数据并使用进度条插件实现进度的可视化。
③ 前端实现
Project/webapp/templates/index.html
{% extends "base.html" %}
{% block content %}
{{ super() }}
<!-- START CONTENT -->
<div class="content">
<!-- START CONTAINER -->
<div class="container-padding">
<div class="line row">
<h2>Long running task with progress updates</h2>
<button id="start-bg-job">Start Long Calculation</button><br><br>
<div id="progress"></div>
</div>
</div>
<!-- END CONTAINER -->
</div>
<!-- End Content -->
{% endblock %}
{% block scripts %}
{{ super() }}
<!-- nanobar -->
<script type="text/javascript" src="{{ url_for('static', filename='js/nanobar/nanobar.min.js') }}"></script>
<script>
// 按钮点击事件
$(function () {
$('#start-bg-job').click(start_long_task);
});
// 请求 longtask接口
function start_long_task() {
// 添加元素到html中
div = $('<div class="progress" style="height:68px"><div></div><div>0%</div><div>...</div><div> </div></div><hr>');
$('#progress').append(div);
// 创建进度条对象
var nanobar = new Nanobar({
bg: '#44f',
target: div[0].childNodes[0]
});
// ajax 请求longtask
$.ajax({
type: 'GET',
url: '/longtask',
// 获得数据,从响应头中获取Location
success: function (data, status, request) {
let status_url = request.getResponseHeader('Location');
// 调用update_progress() 方法更新进度条
update_progress(status_url, nanobar, div[0]);
},
error: function () {
alert('Unexpected error');
}
});
}
// 更新进度条
function update_progress(status_url, nanobar, status_div) {
// getJson()方法是JQuery内置方法,这里向Location中对应的url发起请求,即请求/status/<task_id>
$.getJSON(status_url, function (data) {
// 计算进度
let percent = parseInt(data['current'] * 100 / data['total']);
// 更新进度条
nanobar.go(percent);
// 更新文字
$(status_div.childNodes[1]).text(percent + '%');
$(status_div.childNodes[2]).text(data['status']);
if (data['state'] !== 'PENDING' && data['state'] !== 'PROGRESS') {
if ('result' in data) {
// 展示结果
$(status_div.childNodes[3]).text('Result:' + data['result']);
} else {
// 意料之外的事情发生
$(status_div.childNodes[3]).text('Result:' + data['state'])
}
} else {
// 1秒后再次运行
setTimeout(function () {
update_progress(status_url, nanobar, status_div);
}, 1000)
}
});
}
</script>
{% endblock %}
前端使用nanobar.js来显示进度条,定义了两个函数,start_long_task访问/longtask来启动任务,递归函数update_progress来更新任务处理进度。
前端没什么好说的,注释已经写的很清楚了。
6、 实际运行
由系统原理可以看到,系统想要运行起来首先就要启动Broker服务器和Backend服务器,然后启动Worker,最后才启动Flask服务器。
① 启动Broker和Backend服务器
本文中这两者都是使用的Redis,所以只需要启动Redis-server就可以了
这里有个坑,就是安装好redis后,直接启动是会报错的
(venv) D:\PythonWS\Project>redis-server
[7960] 08 Oct 07:59:35.174 Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
[7960] 08 Oct 07:59:35.184 Creating Server TCP listening socket *:6379: bind: No such file or directory
解决办法是要先在redis-cli中把redis给shutdown之后再启动,如果在redis安装目录里的redis.windows-service.conf设置了密码的话还要输入auth password才能执行shutdown指令
(venv) D:\PythonWS\Project>redis-cli
127.0.0.1:6379> auth "789789"
OK
127.0.0.1:6379> shutdown
not connected> exit
(venv) D:\PythonWS\Project>redis-server
[2232] 08 Oct 08:00:23.429 Warning: no config file specified, using the default config.
In order to specify a config file use redis-server /path/to/redis.conf
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 3.2.100 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 2232
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
至此,我们的Broker和Backend服务器已经启动完毕。
② 启动Worker服务器
使用以下指令启动Worker服务器
celery -A webapp.main.tasks worker -l info -f celery.log --pool=eventlet
webapp.main.tasks 正是celery实例所在的位置,-l info表示消息记录等级,也可以这样写--loglevel=info, -f celery.log表示将log信息写到文件celery.log中, --pool=eventlet表示使用eventlet的线程池进行任务分配,这句很重要在windows环境下如果没有这句会报错的,可能是这个模块在windows下的兼容性不是太好吧!
执行效果如下所示:
(venv) D:\PythonWS\Project>celery -A webapp.main.tasks worker -l info -f celery.log --pool=eventlet
-------------- celery@Matsuri v4.3.0 (rhubarb)
---- **** -----
--- * *** * -- Windows-7-6.1.7601-SP1 2019-10-09 13:28:21
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> app: webapp:0x7108710
- ** ---------- .> transport: redis://localhost:6379/0
- ** ---------- .> results: redis://localhost:6379/1
- *** --- * --- .> concurrency: 4 (eventlet)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. webapp.main.tasks.long_task
注意:Wroker的并发性(concurrency)是4,这个能不能改我还不知道,后续研究了以后再补上。
在celery.log中可以看到以下信息:
[2019-10-09 13:28:21,185: INFO/MainProcess] Connected to redis://localhost:6379/0
[2019-10-09 13:28:21,194: INFO/MainProcess] mingle: searching for neighbors
[2019-10-09 13:28:23,214: INFO/MainProcess] mingle: all alone
[2019-10-09 13:28:23,225: INFO/MainProcess] celery@Matsuri ready.
[2019-10-09 13:28:23,240: INFO/MainProcess] pidbox: Connected to redis://localhost:6379/0.
至此,我们的Wroker服务器也启动起来了。
③ 启动Flask服务器
(venv) D:\PythonWS\Project>python manage.py server
* Serving Flask app "webapp" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 689-169-902
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
至此,所有的准备工作都已经完成,可以在浏览器中输入我们的地址了。
④ 显示效果
点击Start Long Calculation按钮5次,页面上将生成5个进度条,可以看到前四个已经在执行了,最后一个处于挂起状态,这是由Wroker的并发性决定的。
我们最后再来看看后端的提示信息。
首先看看redis:
[2232] 09 Oct 18:24:30.496 * Background saving started by pid 11924
[2232] 09 Oct 18:24:30.596 # fork operation complete
[2232] 09 Oct 18:24:30.596 * Background saving terminated with success
[2232] 09 Oct 18:29:31.016 * 100 changes in 300 seconds. Saving...
[2232] 09 Oct 18:29:31.019 * Background saving started by pid 11900
[2232] 09 Oct 18:29:31.219 # fork operation complete
[2232] 09 Oct 18:29:31.219 * Background saving terminated with success
然后看看Flask后台:
127.0.0.1 - - [09/Oct/2019 18:24:30] "GET /longtask HTTP/1.1" 202 -
127.0.0.1 - - [09/Oct/2019 18:24:30] "GET /status/4b3edb32-5626-4904-bf1b-d7dc2057715c HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:30] "GET /longtask HTTP/1.1" 202 -
127.0.0.1 - - [09/Oct/2019 18:24:30] "GET /status/9ea6b9c6-4aa9-428b-b4f5-8dfcd98f03b8 HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:31] "GET /status/4b3edb32-5626-4904-bf1b-d7dc2057715c HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:31] "GET /longtask HTTP/1.1" 202 -
127.0.0.1 - - [09/Oct/2019 18:24:31] "GET /status/b069a080-d669-4c65-aacf-dcc4ca03239b HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:31] "GET /status/9ea6b9c6-4aa9-428b-b4f5-8dfcd98f03b8 HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:32] "GET /longtask HTTP/1.1" 202 -
127.0.0.1 - - [09/Oct/2019 18:24:32] "GET /status/6dec5d07-3ab8-4a8d-8e10-f28b8eb1b21e HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:32] "GET /status/4b3edb32-5626-4904-bf1b-d7dc2057715c HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:32] "GET /status/b069a080-d669-4c65-aacf-dcc4ca03239b HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:32] "GET /longtask HTTP/1.1" 202 -
127.0.0.1 - - [09/Oct/2019 18:24:32] "GET /status/49f5c67d-9636-4aaf-bbd9-7a40c45c535d HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:32] "GET /status/9ea6b9c6-4aa9-428b-b4f5-8dfcd98f03b8 HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:33] "GET /status/6dec5d07-3ab8-4a8d-8e10-f28b8eb1b21e HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:33] "GET /status/4b3edb32-5626-4904-bf1b-d7dc2057715c HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:33] "GET /status/b069a080-d669-4c65-aacf-dcc4ca03239b HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:33] "GET /status/49f5c67d-9636-4aaf-bbd9-7a40c45c535d HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:33] "GET /status/9ea6b9c6-4aa9-428b-b4f5-8dfcd98f03b8 HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:34] "GET /status/6dec5d07-3ab8-4a8d-8e10-f28b8eb1b21e HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:34] "GET /status/4b3edb32-5626-4904-bf1b-d7dc2057715c HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:34] "GET /status/b069a080-d669-4c65-aacf-dcc4ca03239b HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:34] "GET /status/49f5c67d-9636-4aaf-bbd9-7a40c45c535d HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:34] "GET /status/9ea6b9c6-4aa9-428b-b4f5-8dfcd98f03b8 HTTP/1.1" 200 -
127.0.0.1 - - [09/Oct/2019 18:24:35] "GET /status/6dec5d07-3ab8-4a8d-8e10-f28b8eb1b21e HTTP/1.1" 200 -
最后看看celery.log 里的内容:
[2019-10-09 18:24:30,408: INFO/MainProcess] Received task: webapp.main.tasks.long_task[4b3edb32-5626-4904-bf1b-d7dc2057715c]
[2019-10-09 18:24:30,792: INFO/MainProcess] Received task: webapp.main.tasks.long_task[9ea6b9c6-4aa9-428b-b4f5-8dfcd98f03b8]
[2019-10-09 18:24:31,496: INFO/MainProcess] Received task: webapp.main.tasks.long_task[b069a080-d669-4c65-aacf-dcc4ca03239b]
[2019-10-09 18:24:32,001: INFO/MainProcess] Received task: webapp.main.tasks.long_task[6dec5d07-3ab8-4a8d-8e10-f28b8eb1b21e]
[2019-10-09 18:24:32,563: INFO/MainProcess] Received task: webapp.main.tasks.long_task[49f5c67d-9636-4aaf-bbd9-7a40c45c535d]
[2019-10-09 18:24:45,730: INFO/MainProcess] Task webapp.main.tasks.long_task[6dec5d07-3ab8-4a8d-8e10-f28b8eb1b21e] succeeded in 13.728000000002794s: {'current': 100, 'total': 100, 'status': 'Task Completed!', 'result': '完成'}
[2019-10-09 18:24:50,746: INFO/MainProcess] Task webapp.main.tasks.long_task[4b3edb32-5626-4904-bf1b-d7dc2057715c] succeeded in 20.32699999999022s: {'current': 100, 'total': 100, 'status': 'Task Completed!', 'result': '完成'}
[2019-10-09 18:24:53,878: INFO/MainProcess] Task webapp.main.tasks.long_task[b069a080-d669-4c65-aacf-dcc4ca03239b] succeeded in 22.386000000013155s: {'current': 100, 'total': 100, 'status': 'Task Completed!', 'result': '完成'}
[2019-10-09 18:24:56,217: INFO/MainProcess] Task webapp.main.tasks.long_task[9ea6b9c6-4aa9-428b-b4f5-8dfcd98f03b8] succeeded in 25.427999999999884s: {'current': 100, 'total': 100, 'status': 'Task Completed!', 'result': '完成'}
[2019-10-09 18:25:11,115: INFO/MainProcess] Task webapp.main.tasks.long_task[49f5c67d-9636-4aaf-bbd9-7a40c45c535d] succeeded in 25.396999999997206s: {'current': 100, 'total': 100, 'status': 'Task Completed!', 'result': '完成'}
最终的结果和我们想要的一样,如果在启动地方需要使用Celery,也可以这样进行处理。