57

在 Dart UI 中,我有一个按钮 [submit] 来启动一个长的异步请求。[submit] 处理程序返回一个 Future。接下来,将按钮 [提交] 替换为按钮 [取消] 以允许取消整个操作。在 [cancel] 处理程序中,我想取消长操作。如何取消提交处理程序返回的 Future?我发现没有办法做到这一点。

4

11 回答 11

90

您可以使用CancelableOperationCancelableCompleter取消未来。请参阅下面的 2 个版本:

解决方案1:(CancelableOperation包含在测试中,因此您可以自己尝试):

  • 取消未来

test("CancelableOperation with future", () async {

  var cancellableOperation = CancelableOperation.fromFuture(
    Future.value('future result'),
    onCancel: () => {debugPrint('onCancel')},
  );

// cancellableOperation.cancel();  // uncomment this to test cancellation

  cancellableOperation.value.then((value) => {
    debugPrint('then: $value'),
  });
  cancellableOperation.value.whenComplete(() => {
    debugPrint('onDone'),
  });
});
  • 取消流

test("CancelableOperation with stream", () async {

  var cancellableOperation = CancelableOperation.fromFuture(
    Future.value('future result'),
    onCancel: () => {debugPrint('onCancel')},
  );

  //  cancellableOperation.cancel();  // uncomment this to test cancellation

  cancellableOperation.asStream().listen(
        (value) => { debugPrint('value: $value') },
    onDone: () => { debugPrint('onDone') },
  );
});

上述两个测试都将输出:

then: future result
onDone

现在,如果我们取消注释,cancellableOperation.cancel();那么上述两个测试都将输出:

onCancel

解决方案2:(CancelableCompleter如果您需要更多控制)

test("CancelableCompleter is cancelled", () async {

  CancelableCompleter completer = CancelableCompleter(onCancel: () {
    print('onCancel');
  });

  // completer.operation.cancel();  // uncomment this to test cancellation

  completer.complete(Future.value('future result'));
  print('isCanceled: ${completer.isCanceled}');
  print('isCompleted: ${completer.isCompleted}');
  completer.operation.value.then((value) => {
    print('then: $value'),
  });
  completer.operation.value.whenComplete(() => {
    print('onDone'),
  });
});

输出:

isCanceled: false
isCompleted: true
then: future result
onDone

现在,如果我们取消注释,cancellableOperation.cancel();我们会得到输出:

onCancel
isCanceled: true
isCompleted: true

请注意,如果您使用await cancellableOperation.valueorawait completer.operation那么未来将永远不会返回结果,并且如果操作被取消,它将无限期地等待。这是因为await cancellableOperation.value与写入相同,cancellableOperation.value.then(...)then()如果操作被取消,则永远不会被调用。

记得添加异步Dart 包。

代码要点

于 2019-02-27T12:48:53.260 回答
21

如何取消Future.delayed

一个简单的方法是Timer改用:)

Timer _timer;

void _schedule() {
  _timer = Timer(Duration(seconds: 2), () { 
    print('Do something after delay');
  });
}

@override
void dispose() {
  super.dispose();
  _timer?.cancel();
}
于 2020-04-23T10:36:50.887 回答
12

据我所知,没有办法取消未来。但是有一种方法可以取消 Stream 订阅,也许这可以帮助您。

调用onSubmit按钮会返回一个StreamSubscription对象。您可以显式存储该对象,然后调用cancel()它来取消流订阅:

StreamSubscription subscription = someDOMElement.onSubmit.listen((data) {

   // you code here

   if (someCondition == true) {
     subscription.cancel();
   }
});

稍后,作为对某些用户操作的响应,您也许可以取消订阅:

于 2013-07-11T21:33:06.723 回答
7

我完成“取消”计划执行的一种方法是使用Timer. 在这种情况下,我实际上推迟了它。:)

Timer _runJustOnceAtTheEnd;

void runMultipleTimes() {
  _runJustOnceAtTheEnd?.cancel();
  _runJustOnceAtTheEnd = null;

  // do your processing

  _runJustOnceAtTheEnd = Timer(Duration(seconds: 1), onceAtTheEndOfTheBatch);
}

void onceAtTheEndOfTheBatch() {
  print("just once at the end of a batch!");
}


runMultipleTimes();
runMultipleTimes();
runMultipleTimes();
runMultipleTimes();

// will print 'just once at the end of a batch' one second after last execution

runMultipleTimes()方法将按顺序调用多次,但仅在批处理的 1 秒后onceAtTheEndOfTheBatch才会执行。

于 2018-12-06T18:34:46.220 回答
7

对于那些试图在 Flutter 中实现这一点的人,这里是一个简单的例子。

class MyPage extends StatelessWidget {
  final CancelableCompleter<bool> _completer = CancelableCompleter(onCancel: () => false);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Future")),
      body: Column(
        children: <Widget>[
          RaisedButton(
            child: Text("Submit"),
            onPressed: () async {
              // it is true only if the future got completed
              bool _isFutureCompleted = await _submit();
            },
          ),
          RaisedButton(child: Text("Cancel"), onPressed: _cancel),
        ],
      ),
    );
  }

  Future<bool> _submit() async {
    _completer.complete(Future.value(_solve()));
    return _completer.operation.value;
  }

  // This is just a simple method that will finish the future in 5 seconds
  Future<bool> _solve() async {
    return await Future.delayed(Duration(seconds: 5), () => true);
  }

  void _cancel() async {
    var value = await _completer.operation.cancel();
    // if we stopped the future, we get false
    assert(value == false);
  }
}
于 2019-05-29T13:22:04.223 回答
4

我的 2 美分值...

class CancelableFuture {
  bool cancelled = false;
  CancelableFuture(Duration duration, void Function() callback) {
    Future<void>.delayed(duration, () {
      if (!cancelled) {
        callback();
      }
    });
  }

  void cancel() {
    cancelled = true;
  }
}
于 2019-08-14T23:26:10.840 回答
3

pub.dev上的async 包中有一个CancelableOperation,您现在可以使用它来执行此操作。不要将此包与没有此类的内置 dart 核心库混淆dart:async

于 2020-11-17T23:30:25.273 回答
1

将未来的任务从“做某事”改为“做某事,除非它被取消”。实现这一点的一个明显方法是设置一个布尔标志并在开始处理之前的未来关闭中检查它,也许在处理过程中的几个点。

此外,这似乎有点小技巧,但是将未来的超时设置为零似乎可以有效地取消未来。

于 2014-08-04T22:16:46.617 回答
1

下面的代码有助于设计未来的超时功能,可以手动取消。

import 'dart:async';

class API {
  Completer<bool> _completer;
  Timer _timer;

  // This function returns 'true' only if timeout >= 5 and
  // when cancelOperation() function is not called after this function call.
  //
  // Returns false otherwise
  Future<bool> apiFunctionWithTimeout() async {
    _completer = Completer<bool>();
    // timeout > time taken to complete _timeConsumingOperation() (5 seconds)
    const timeout = 6;

    // timeout < time taken to complete _timeConsumingOperation() (5 seconds)
    // const timeout = 4;

    _timeConsumingOperation().then((response) {
      if (_completer.isCompleted == false) {
        _timer?.cancel();
        _completer.complete(response);
      }
    });

    _timer = Timer(Duration(seconds: timeout), () {
      if (_completer.isCompleted == false) {
        _completer.complete(false);
      }
    });

    return _completer.future;
  }

  void cancelOperation() {
    _timer?.cancel();
    if (_completer.isCompleted == false) {
      _completer.complete(false);
    }
  }

  // this can be an HTTP call.
  Future<bool> _timeConsumingOperation() async {
    return await Future.delayed(Duration(seconds: 5), () => true);
  }
}

void main() async {
  API api = API();
  api.apiFunctionWithTimeout().then((response) {
    // prints 'true' if the function is not timed out or canceled, otherwise it prints false
    print(response);
  });
  // manual cancellation. Uncomment the below line to cancel the operation.
  //api.cancelOperation();
}

返回类型可以更改bool为您自己的数据类型。Completer对象也应该相应地改变。

于 2020-08-21T11:01:32.290 回答
0

这是取消可等待的延迟未来的解决方案

这个解决方案就像awaitableTimercancelableFuture.delayed:它像 a一样可取消Timer,并且像 a一样可等待Future

它基于一个非常简单的类CancelableCompleter,这里有一个演示:

import 'dart:async';

void main() async {  
  print('start');
  
  // Create a completer that completes after 2 seconds…
  final completer = CancelableCompleter.auto(Duration(seconds: 2));
  
  // … but schedule the cancelation after 1 second
  Future.delayed(Duration(seconds: 1), completer.cancel);
  
  // We want to await the result
  final result = await completer.future;

  print(result ? 'completed' : 'canceled');
  print('done');
  // OUTPUT:
  //  start
  //  canceled
  //  done
}

现在类的代码:

class CancelableCompleter {
  CancelableCompleter.auto(Duration delay) : _completer = Completer() {
    _timer = Timer(delay, _complete);
  }

  final Completer<bool> _completer;
  late final Timer? _timer;

  bool _isCompleted = false;
  bool _isCanceled = false;

  Future<bool> get future => _completer.future;

  void cancel() {
    if (!_isCompleted && !_isCanceled) {
      _timer?.cancel();
      _isCanceled = true;
      _completer.complete(false);
    }
  }

  void _complete() {
    if (!_isCompleted && !_isCanceled) {
      _isCompleted = true;
      _completer.complete(true);
    }
  }
}

此 DartPad中提供了一个具有更完整类的运行示例。

于 2021-09-23T16:18:41.177 回答
0

一个从未来取消注册回调的小类。此类不会阻止执行,但可以在您需要切换到具有相同类型的另一个未来时提供帮助。不幸的是我没有测试它,但是:

class CancelableFuture<T> {
  Function(Object) onErrorCallback;
  Function(T) onSuccessCallback;
  bool _wasCancelled = false;

  CancelableFuture(Future<T> future,
      {this.onSuccessCallback, this.onErrorCallback}) {
    assert(onSuccessCallback != null || onErrorCallback != null);
    future.then((value) {
      if (!_wasCancelled && onSuccessCallback != null) {
        onSuccessCallback(value);
      }
    }, onError: (e) {
      if (!_wasCancelled && onErrorCallback != null) {
        onErrorCallback(e);
      }
    });
  }

  cancel() {
    _wasCancelled = true;
  }
}

这是使用示例。PS我在我的项目中使用提供者:

_fetchPlannedLists() async {
    if (_plannedListsResponse?.status != Status.LOADING) {
      _plannedListsResponse = ApiResponse.loading();
      notifyListeners();
    }

    _plannedListCancellable?.cancel();

    _plannedListCancellable = CancelableFuture<List<PlannedList>>(
        _plannedListRepository.fetchPlannedLists(),
        onSuccessCallback: (plannedLists) {
      _plannedListsResponse = ApiResponse.completed(plannedLists);
      notifyListeners();
    }, onErrorCallback: (e) {
      print('Planned list provider error: $e');
      _plannedListsResponse = ApiResponse.error(e);
      notifyListeners();
    });
  }

您可以在以下情况下使用它,当语言发生变化并提出请求时,您不关心先前的响应并提出另一个请求!此外,我真的很想知道这个功能不是来自盒子。

于 2021-07-08T17:40:29.853 回答