如何在Dart中取消文件读取操作?解决RandomAccessFile异步冲突
解决Dart RandomAccessFile并发读取异常的方案
问题核心是RandomAccessFile不支持并发异步操作,旧的读取任务未完成时发起新操作就会抛出FileSystemException。要解决这个问题,你需要主动管理并取消正在进行的读取任务,确保同一时间只有一个读取操作在执行。
具体实现步骤
1. 引入async包(推荐)
使用CancelableOperation来封装读取任务,它能方便地取消正在执行的异步操作。在pubspec.yaml中添加依赖:
dependencies: async: ^2.11.0
2. 封装文件流管理类
维护一个跟踪当前读取任务的变量,每次发起新读取前先取消旧任务:
import 'dart:io'; import 'dart:async'; import 'package:async/async.dart'; class VideoFileStreamer { RandomAccessFile? _file; CancelableOperation? _activeReadTask; // 打开目标文件 Future<void> initFile(String filePath) async { _file = await File(filePath).open(mode: FileMode.read); } // 响应进度拖动,跳转到指定位置读取并传输 Future<void> seekAndStream(int targetPosition) async { // 取消正在进行的读取任务 if (_activeReadTask != null && !_activeReadTask!.isCompleted) { _activeReadTask!.cancel(); // 等待取消操作完成,避免资源冲突 await _activeReadTask!.valueOrCancellation(); } try { // 将文件指针定位到目标位置 await _file!.setPosition(targetPosition); // 创建新的可取消读取任务 _activeReadTask = CancelableOperation.fromFuture( _streamFileChunks(), onCancel: () => print('旧读取任务已取消'), ); await _activeReadTask!.value; } catch (e) { // 忽略取消操作导致的异常,处理其他真实错误 if (e is! CancelException) { print('文件读取失败: $e'); } } } // 分块读取文件并传输到HTTP响应 Future<void> _streamFileChunks() async { const chunkSize = 4096; // 每次读取4KB块 final buffer = List<int>.filled(chunkSize, 0); while (true) { // 检查任务是否被取消,若取消则终止循环 if (_activeReadTask?.isCanceled ?? false) break; final bytesRead = await _file!.readInto(buffer); if (bytesRead == 0) break; // 文件读取完毕 // 将读取的块发送到HTTP响应流(替换成你的实际传输逻辑) // response.add(buffer.sublist(0, bytesRead)); } } // 资源清理 Future<void> dispose() async { if (_activeReadTask != null) { _activeReadTask!.cancel(); await _activeReadTask!.valueOrCancellation(); } await _file?.close(); } }
3. 关键逻辑说明
- 任务取消:每次调用
seekAndStream时,先检查并取消未完成的旧任务,确保文件资源被释放后再执行新操作。 - 可取消任务:用
CancelableOperation.fromFuture包裹读取循环,让异步任务具备可取消能力。 - 循环内检查:在读取循环中实时检查任务是否被取消,避免无效的读取操作。
替代方案(不依赖async包)
如果不想引入第三方包,可以自己用Completer和bool标记来控制任务终止:
class VideoFileStreamer { RandomAccessFile? _file; bool _isReading = false; Completer<void>? _readCompleter; Future<void> seekAndStream(int targetPosition) async { if (_isReading) { _isReading = false; await _readCompleter?.future; } _isReading = true; _readCompleter = Completer(); try { await _file!.setPosition(targetPosition); await _streamFileChunks(); } catch (e) { print('读取错误: $e'); } finally { _isReading = false; _readCompleter?.complete(); } } Future<void> _streamFileChunks() async { const chunkSize = 4096; final buffer = List<int>.filled(chunkSize, 0); while (_isReading) { final bytesRead = await _file!.readInto(buffer); if (bytesRead == 0) break; // 传输逻辑 // response.add(buffer.sublist(0, bytesRead)); } } }
这种方式通过_isReading标记控制循环终止,缺点是无法真正中断正在执行的readInto操作,只能等当前读取完成后停止后续循环,不如CancelableOperation高效。
内容的提问来源于stack exchange,提问作者MOHAMMAD RASIM
相关产品推荐
相关产品推荐

