耗时HTTP请求的实时进度展示实现思路

问题说明

在进行业务开发时,有时会面临耗时请求。也就是前端发给后端的请求,后端需要一定时间进行处理,几分钟或十几分钟,远远超出了一般HTTP请求的timeout。如果不加处理,会带来不好的用户体验。

解决思路

  1. 面对耗时请求,后端开辟额外进程进行处理,立即返回已经开始处理的信息给前端。
  2. 前端轮询处理进度,更新页面UI,直到处理完成。(也可以改用WebSocket协议,让后端推送处理进度给前端)

实现案例

前端技术栈: Vue2 + ElementUI
后端技术栈:Python3 + Flask

Python 多进程及进程间通讯

  • 耗时请求属于计算密集型任务,基于Python语言的特性,选择多进程而不是多线程。
  • 进程间通讯有管道Pipe、队列Queue以及共享内存Manager,基于仅需要记录进度,主进程和子进程不需要过多的通讯,选择了共享内存。
from multiprocessing import Process
from multiprocessing.sharedctypes import Value

# 全局变量,其中:Value为共享内存变量
# 因为耗时请求和轮询请求属于不同的请求,所以不能用Flask的g
# 又因为共享内存变量无法Json化,所以不能用Flask的session
update_progress_info = {
    # 全部数据
    'total': 0,
    # 以及处理的数据
    'done': Value('i', 0),
    # 状态:0 表示结束、未启动;1 表示进行中
    'status': Value('i', 0)
}

# 后台子进程函数
def _update_all_person_tags(numbers, progress_info):
    import time
    for number in numbers:
        progress_info['done'].value += 1
        # 模拟密集计算
        time.sleep(0.01)
    # 处理完成,设置状态
    progress_info['status'].value = 0

# 耗时请求处理函数
def update_all_person_tag():
    if update_progress_info['status'].value:
        return {'msg': '成功', 'code': 0, 'data': '更新程序已启动!'}
    # 查询所有员工工号
    mysql_cursor = db.connect_mysql()
    sql = "select number from person"
    mysql_cursor.execute(sql)
    numbers = [number[0] for number in mysql_cursor.fetchall()]
	# 修改全局变量
    update_progress_info['total'] = len(numbers)
    update_progress_info['done'].value = 0
    update_progress_info['status'].value = 1
    # 配置子进程
    update_process = Process(target=_update_all_person_tags,
                             args=(numbers, update_progress_info))
    # 配置其为守护进程,让主进程不必等待其结束
    update_process.daemon = True
    # 启动
    update_process.start()
    print(f"开启后台进程,其进程号为{update_process.pid}")
    return {'msg': '成功', 'code': 0, 'data': '更新程序已启动!'}

JavaScript轮询

  • 为了不阻塞页面,采用基于setTimeout的异步请求
  • 只有在当前请求完成后,才嵌套调用下一次请求
 askProgress() {
     tagApi.askProgress().then(
         (res) => {
         	 // 每次接收后端返回的进度数据,更新UI
             this.totalNum = res.data.total
             this.updatedNum = res.data.done
             this.updateStatus = res.data.status
             // 判断是否更新完成
             if (this.updateStatus == 0) {
             	 // 提示更新完成
                 Message({
                     message: '更新已完成!',
                     type: 'success',
                     duration: 2 * 1000
                 })
             } else {
             	 // 未完成,则开启新一轮问询
                 this.timeoutId = setTimeout(() => {
                     clearTimeout(this.timeoutId)
                     this.askProgress()
                 }, 500)
             }
         },
         (err) => {
             console.log('err', err)
         }
     )
 },
 updatePersonTags() {
     // 发请求通知后端开始更新
     tagApi.updateAllPersonTag().then(
         (res) => {
             Message({
                 message: res.data,
                 type: 'success',
                 duration: 2 * 1000
             })
             // 开始轮询更新进度条
             this.askProgress()
         },
         (err) => {
             console.log('err', err)
         }
     )
 }

效果

结合ElementUI的进度条组件,实现效果如下:
演示

总结

结合多进程和轮询,完成了耗时请求处理进度的前端实时显示功能,存在以下可改进的地方:

  1. 耗时的处理进程目前是单进程,可使用多个进程同时对耗时任务进行处理,充分利用CPU的性能。
  2. 前端的轮询代码是嵌套调用,可采用Promise语言特性避免嵌套回调。

参考资料

  1. 知乎 一篇文章搞定Python多进程(全)
  2. 知乎 在Python中优雅地用多进程
  3. 掘金 vue轮询解决方案
  4. 掘金 前端Promise轮询
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值