示例说明
终端进度渲染的难点是进度条与普通日志抢同一块屏幕。MultiProgress 管理多条进度条的布局,println 方法在打印日志行时暂停进度重绘——输出交错不再撕裂。不同阶段选不同形态:长任务用带 ETA 的进度条,毫秒级收尾用 spinner,避免为微小操作渲染完整条带。
技术要点
- MultiProgress 统一布局多条进度条
- println 打日志时暂停进度重绘
- 短任务退化 spinner 减少噪音
代码解读
两条进度条分别走四百毫秒与三百毫秒的模拟阶段,样式模板定制字符集与配色;inc 逐项推进,finish_with_message 收尾并定格消息。checksum 的 wrapping_add 展示阶段产出。最后的 clear 清理终端残留,脚本输出干净结束。
使用建议
进度刷新频率默认限速已合理无需自调。非 TTY 环境自动降级为静默。长时间任务配 suspend 写日志比清屏打印更稳妥。
代码示例
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
// progress logging: println pauses bar redraw to keep output clean
use std::time::Duration;
fn make_style(template: &str) -> ProgressStyle {
ProgressStyle::with_template(template)
.unwrap()
.progress_chars("=>-")
}
async fn process_stage(
bar: &ProgressBar,
total: u64,
item_ms: u64,
) -> u64 {
let mut checksum = 0u64;
for i in 0..total {
tokio::time::sleep(Duration::from_millis(item_ms)).await;
checksum = checksum.wrapping_add(i * 31);
bar.inc(1);
}
checksum
}
#[tokio::main]
async fn main() {
let multi = MultiProgress::new();
let download = multi.add(ProgressBar::new(40));
download.set_style(make_style(
"[{bar:40.cyan/blue}] {pos}/{len} files ({eta})",
));
let verify = multi.add(ProgressBar::new(20));
verify.set_style(make_style(
"[{bar:40.green/white}] {pos}/{len} chunks {msg}",
));
// 进度条与日志共存:suspend 暂停渲染打印普通输出
multi.println("starting pipeline").unwrap();
let sum1 = process_stage(&download, 40, 10).await;
download.finish_with_message("download complete");
verify.set_message("verifying");
let sum2 = process_stage(&verify, 20, 15).await;
multi.println(format!("checksums: {sum1} / {sum2}")).unwrap();
// 短任务用 spinner:避免为毫秒级操作渲染完整进度条
let spinner = multi.add(ProgressBar::new_spinner());
spinner.set_style(make_style("{spinner:.magenta} {msg}"));
spinner.set_message("finalizing");
tokio::time::sleep(Duration::from_millis(300)).await;
spinner.finish_with_message("done");
multi.clear().unwrap();
}