耗时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轮询
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要在Java中实现实时读取进度的功能,你可以使用线程和回调函数来实现。下面是一个简单的示例: 首先,定义一个接口 `ProgressCallback`,其中包含一个用于接收进度更新的回调方法 `onProgressUpdate`: ```java public interface ProgressCallback { void onProgressUpdate(int progress); } ``` 然后,在你的主程序中创建一个实现了该接口的类,并实现 `onProgressUpdate` 方法,以便在每次更新进度时执行相应的操作: ```java public class ProgressBar implements ProgressCallback { @Override public void onProgressUpdate(int progress) { // 在这里更新进度条或执行其他操作 System.out.println("当前进度:" + progress + "%"); } } ``` 接下来,在你的主程序中创建一个线程来模拟进度更新,并在每次更新时调用回调方法: ```java public class Main { public static void main(String[] args) { ProgressBar progressBar = new ProgressBar(); // 创建一个线程来模拟进度更新 Thread progressThread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i <= 100; i++) { // 模拟耗时操作 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 更新进度 progressBar.onProgressUpdate(i); } } }); // 启动线程 progressThread.start(); } } ``` 在上面的示例中,`ProgressBar` 类实现了 `ProgressCallback` 接口,并在 `onProgressUpdate` 方法中打印当前进度。然后,创建一个线程 `progressThread` 来模拟进度更新,每次更新时调用 `progressBar.onProgressUpdate` 方法。 当你运行上述代码时,你将看到每秒钟输出一次当前进度的消息,模拟了实时读取进度的功能。你可以根据实际需求,在 `onProgressUpdate` 方法中执行你想要的操作,比如更新进度条的显示或执行其他相关任务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值