如何在Dart命令行程序中实现堆叠式并行进度条
解决Dart Isolate并行进度条堆叠更新问题
在Dart编译农场场景中,10个Isolate并发处理250个固件构建任务,每个Isolate串行打印无换行进度条时会互相覆盖。要实现进度条堆叠显示且可单独更新,同时不使用清屏命令,可通过ANSI终端控制序列+固定行号映射来实现:
核心思路
- 给每个构建任务分配固定的终端行位置,初始时先打印所有任务的占位进度条,记录每个任务对应的行号。
- 使用ANSI光标移动命令定位到目标行,清除该行原有内容后打印更新后的进度条,避免干扰其他行的内容。
实现代码
定义ANSI终端控制码
// ANSI终端控制序列:清除当前行(从光标到行尾) const ansiClearLine = '\x1B[K'; // 光标向上移动n行的模板,替换%d为行数 const ansiCursorUp = '\x1B[%dA'; // 光标向下移动n行的模板,替换%d为行数 const ansiCursorDown = '\x1B[%dB';
初始化进度条行映射
在主Isolate中预先创建所有任务的占位行,并记录每个任务对应的行号(如果任务是动态创建的,可在任务启动时执行此步骤):
// 存储任务ID到终端行号的映射,需保证线程安全,可配合Mutex使用 final Map<String, int> taskLineMap = {}; int currentLine = 0; // 初始化所有任务的占位进度条 void initTaskProgressBars(List<String> allTaskIds, String module) { for (final taskId in allTaskIds) { // 打印初始进度条(0%) stdout.write('$module: task ID $taskId: ${''.padLeft(40, '•')} 0%\n'); // 记录当前任务对应的行号 taskLineMap[taskId] = currentLine++; } // 光标回到所有进度条的上方,准备后续更新 stdout.write(ansiCursorUp.replaceFirst('%d', currentLine.toString())); }
单个任务的进度更新逻辑
在每个Isolate的任务处理函数中,调用此方法更新对应任务的进度条:
void updateTaskProgress(String module, String taskId, int progress) { const progressBarLength = 40; final completedLength = (progress * progressBarLength / 100).floor(); final remainingLength = progressBarLength - completedLength; // 获取当前任务对应的终端行号 final targetLine = taskLineMap[taskId]!; // 移动光标到目标行 stdout.write(ansiCursorDown.replaceFirst('%d', targetLine.toString())); // 清除当前行原有内容 stdout.write(ansiClearLine); if (progress == 100) { // 任务完成时打印完成状态 stdout.write('$module: task ID $taskId: 🟢 Completed\n'); } else { // 打印更新后的进度条 stdout.write( '$module: task ID $taskId: ${''.padLeft(completedLength, '=')}${''.padLeft(remainingLength, '•')} ${progress.toStringAsFixed(0)}%', ); } // 光标回到进度条区域的上方,不影响后续其他任务的更新 stdout.write(ansiCursorUp.replaceFirst('%d', targetLine.toString())); }
注意事项
- 确保运行环境的终端支持ANSI控制序列:Linux/macOS终端、Windows Terminal均支持,旧版Windows命令提示符可能需要开启相关设置。
- 行号映射的线程安全:由于多个Isolate会访问
taskLineMap,需使用Mutex或Dart的跨Isolate通信机制(如IsolateNameServer)保证并发安全。 - 动态任务场景:如果任务不是预先全部创建的,可在每个任务启动时打印占位行并记录行号,避免提前占用过多终端行。
内容的提问来源于stack exchange,提问作者Stéphane de Luca
相关产品推荐
相关产品推荐

