文件IO(Input/Output)是指应用程序与存储设备之间的数据传输操作,在HarmonyOS中,文件IO性能直接影响应用的流畅度和用户体验。
文件IO的本质是一条从CPU到磁盘的请求链路:CPU发起请求,通过系统调用陷入内核,再由内核的页缓存(page cache)落到磁盘(块设备)上。磁盘速度并非决定IO性能的唯一因素。IO性能还取决于页缓存机制、内核调度策略、存储硬件性能与文件系统实现。
原理:应用主线程同步执行耗时逻辑(如文件IO),会阻塞主线程的任务队列,导致渲染和输入事件无法及时处理。
场景:用户触发涉及文件处理或数据同步的业务操作后,应用界面出现明显卡顿。随后应用进入无响应状态,系统可能强制退出该应用。
原理:多个TaskPool工作线程同时执行文件IO,导致IO任务占满全部工作线程,后续业务任务排队等待。
场景:应用需要批量处理大量文件(如扫描上千个文件并校验内容)。为不阻塞主线程,开发者将所有文件处理逻辑封装成多个并发任务,一次性调用taskpool.execute()提交给TaskPool执行。结果所有任务“卡住不动”,没有任何回调返回,工作线程完全阻塞。
FileSystem模板自DevEco Studio 26.0.0版本起支持,低版本DevEco Studio的Profiler中不提供该模板。
下图展示了标准化定位流程:

问题场景及现象:用户在使用应用的过程中,触发某项涉及文件处理或数据同步的业务操作。此后设备出现明显的发热和耗电增加,界面操作响应延迟明显,随后进入无响应状态。
录制IO耗时数据
连接设备并启动应用后,打开DevEco Studio的Profiler,按以下路径操作:选择Profiler → 选择设备与应用进程 → 选择FileSystem模板 → Create Session(创建Session) → 启动录制。录制开始后,在设备上复现问题场景。随后停止录制,等待工具解析并生成IO耗时数据。Profiler录制与Session创建界面如下图所示:

问题分析
查看应用IO耗时数据,框选应用IO主泳道,可以看到在一段时间内,逻辑写80000次,逻辑读80000次。Profiler采集到的应用IO耗时数据如下图所示:

查看应用IO子泳道,Logical Writes展示应用逻辑写的详细数据,Logical Reads展示应用逻辑读的详细数据。可以看到IO条块呈现“先大量逻辑写,再大量逻辑读”的集中分布,这段区间的IO耗时占比明显偏高。应用IO子泳道耗时数据如下图所示:

以逻辑写耗时为例,选中Logical Writes泳道,框选Logical Writes泳道数据,滚动Details区域列表数据,发现所有线程号都是26477,记录下该线程号。Details详情如下图所示:

各泳道采集数据的含义可参考IO耗时:FileSystem分析。
展开Callstack泳道,找到对应的子泳道。在底部详情区选择Top Down页签,查看对应的代码文件。单击Call Trees按钮,在弹出的浮层中勾选Hide System Libraries。双击应用堆栈,即可跳转到对应代码位置。Callstack泳道与调用栈详情如下图所示:

修复与验证
结合源码分析,主线程上循环批量读写文件导致了该问题。
问题源码如下:
runBadSyncIO(): void {
// ...
for (let i = 0; i < SYNC_IO_LINE_COUNT; i++) {
const file = fileIo.openSync(
filePath,
fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE | fileIo.OpenMode.APPEND
);
try {
fileIo.writeSync(file.fd, data);
fileIo.statSync(filePath);
} finally {
fileIo.closeSync(file);
}
}
// ...
for (let i = 0; i < SYNC_IO_LINE_COUNT; i++) {
// Wrap the read offset by the file size so that the file is read line by line and fully covered.
const offset = totalSize > 0 ? (i * readBufferSize) % totalSize : 0;
const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
try {
fileIo.readSync(file.fd, readBuffer, { offset: offset });
fileIo.statSync(filePath);
} finally {
fileIo.closeSync(file);
}
}
// ...
} 建议修复方案:
代码优化后,重新触发原异常场景,使用FileSystem模板录制。可以看到应用逻辑IO次数与耗时均恢复正常:逻辑写与逻辑读各1次,最大耗时1.09ms。优化后的IO耗时统计数据如下图所示:

问题场景及现象:同前述“并发资源耗尽”场景,业务任务占满全部工作线程,后续任务长时间排队等待。
录制IO耗时数据
参考主线程阻塞案例中的第1步录制IO耗时数据。
问题分析
查看应用IO耗时数据。可以看到在一段时间内,逻辑写599979次。并发场景下泳道的Statistics区域详情如下图所示:

框选Logical Writes泳道。该区间的IO耗时占比明显偏高,且同一时刻应用发起的逻辑IO最多达到5次。滚动Details区域列表数据,寻找线程号占比较多的IO数据并记录线程号。并发场景Logical Writes泳道的Details详情如下图所示:

展开Callstack泳道,按上述案例的方法找到对应线程的子泳道,在底部详情区选择Top Down页签并勾选Hide System Libraries,双击应用堆栈即可跳转到对应代码位置。子泳道所对应的应用堆栈如下图所示:

修复与验证
结合源码分析,一次性无限制地提交大量IO任务,占满全部TaskPool工作线程。后续任务因此长时间排队等待,造成资源饥饿和响应延迟。
问题源码如下:
@Concurrent
async function unboundedIoTask(context: common.UIAbilityContext, taskId: number): Promise<number> {
// ...
const start = Date.now();
// ...
for (let i = 0; i < CONCURRENT_IO_LINE_COUNT; i++) {
const file = fileIo.openSync(
filePath,
fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE | fileIo.OpenMode.APPEND
);
try {
fileIo.writeSync(file.fd, `Line ${i}: ${data}`);
} finally {
fileIo.closeSync(file);
}
}
return Date.now() - start;
}
// ...
async runBadConcurrency(): Promise<void> {
const context = this.getUIContext().getHostContext();
const promises: Promise<number>[] = [];
for (let i = 0; i < TASK_COUNT; i++) {
promises.push(taskpool.execute(unboundedIoTask, context, i) as Promise<number>);
}
await Promise.all(promises);
} 建议修复方案:
优化代码后,重新触发原异常场景,使用FileSystem模板录制,可以看到同一时刻仅有3个IO任务在OS_TaskWorker线程上执行,不再占满其余工作线程。受控并发后的执行结果如下图所示:
