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

Dart中如何取消Future?该场景是否有替代方案?

在Dart中取消Future及场景替代方案

一、Dart原生Future能取消吗?

不能。Dart的原生Future一旦启动就会执行到完成,没有内置的取消机制——你只能选择忽略它的结果,没法中途中断正在运行的计算任务。

二、你的场景现有方案分析

你现在用的token标记方案是可行的:每次触发新请求时更新token,计算完成后对比当前token和任务启动时的token,不一致就丢弃结果。但这个方案的问题是:之前的计算任务还是会完整跑完,只是结果不用,会浪费CPU资源。

三、更优的替代方案

1. 使用async包的CancelableOperation

这个包提供了可取消的操作,能在新请求触发时,直接取消之前的任务(如果任务支持响应取消信号的话),避免无效计算。

先添加依赖:async: ^2.11.0(可使用最新版本)

改造你的代码示例:

import 'package:async/async.dart';
import 'package:flutter/foundation.dart';

class A extends ChangeNotifier {
  int _token = 0;
  Data? _data;
  Data? get data => _data;
  CancelableOperation? _currentOperation;

  Future<void> foo() async {
    // 取消之前的任务
    _currentOperation?.cancel();
    
    _token += 1;
    final token = _token;
    
    _currentOperation = CancelableOperation.fromFuture(
      ReallyLongCompute(),
      onCancel: () {
        // 如果计算任务支持主动终止,可以在这里加逻辑,比如设置取消标记
      },
    );

    try {
      final data = await _currentOperation?.value;
      if (_token == token && data != null) {
        _data = data;
        notifyListeners();
      }
    } on CanceledError {
      // 任务被取消,直接忽略
    } finally {
      _currentOperation = null;
    }
  }
}

void ButtonOnClick() async {
  final a = Provider.of<A>(context, listen: false);
  await a.foo();
}

如果你的ReallyLongCompute是可中断的计算(比如循环处理),可以给它加取消检查:

Future<Data> ReallyLongCompute({CancelableOperation? operation}) async {
  Data result = Data();
  for (int i = 0; i < 100000; i++) {
    // 检查是否被取消,是的话提前终止
    if (operation?.isCanceled ?? false) {
      throw CanceledError();
    }
    // 执行计算逻辑
    result.update(i);
  }
  return result;
}

2. 使用Stream的switchMap

如果请求是连续触发的,用Stream配合switchMap可以自动丢弃之前的请求,只处理最新的:

class A extends ChangeNotifier {
  Data? _data;
  Data? get data => _data;
  final _requestController = StreamController<void>();

  A() {
    _requestController.stream
        .switchMap((_) async* {
          yield await ReallyLongCompute();
        })
        .listen((data) {
          _data = data;
          notifyListeners();
        });
  }

  void foo() {
    _requestController.add(null);
  }

  @override
  void dispose() {
    _requestController.close();
    super.dispose();
  }
}

每次调用foo()时,switchMap会取消之前的订阅,旧任务即使跑完也不会触发回调,只处理最新的计算结果。

3. 自定义取消标记

如果不想依赖第三方包,可以自己实现简单的取消标记,在计算任务中定期检查:

class CancelToken {
  bool _isCanceled = false;
  bool get isCanceled => _isCanceled;

  void cancel() => _isCanceled = true;
}

class A extends ChangeNotifier {
  Data? _data;
  Data? get data => _data;
  CancelToken? _currentToken;

  Future<void> foo() async {
    // 取消之前的任务
    _currentToken?.cancel();
    final newToken = CancelToken();
    _currentToken = newToken;

    final data = await ReallyLongCompute(token: newToken);
    // 检查是否被取消
    if (!newToken.isCanceled) {
      _data = data;
      notifyListeners();
    }
  }
}

Future<Data> ReallyLongCompute({required CancelToken token}) async {
  Data result = Data();
  for (int i = 0; i < 100000; i++) {
    if (token.isCanceled) break;
    result.update(i);
  }
  return result;
}

总结

  • 原生Future不能取消,只能忽略结果;
  • 你的token方案是基础实现,但会浪费计算资源;
  • 如果要真正中断计算,推荐用CancelableOperation或自定义取消标记,在计算任务中加入取消检查;
  • 如果只需要丢弃结果,用Stream.switchMap更简洁。

内容的提问来源于stack exchange,提问作者Fractale

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 18:05:04