You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 00:33:31