在 Dart UI 中,我有一个按钮 [submit] 来启动一个长的异步请求。[submit] 处理程序返回一个 Future。接下来,将按钮 [提交] 替换为按钮 [取消] 以允许取消整个操作。在 [cancel] 处理程序中,我想取消长操作。如何取消提交处理程序返回的 Future?我发现没有办法做到这一点。
11 回答
您可以使用CancelableOperation或CancelableCompleter取消未来。请参阅下面的 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.value
orawait completer.operation
那么未来将永远不会返回结果,并且如果操作被取消,它将无限期地等待。这是因为await cancellableOperation.value
与写入相同,cancellableOperation.value.then(...)
但then()
如果操作被取消,则永远不会被调用。
记得添加异步Dart 包。
如何取消Future.delayed
一个简单的方法是Timer
改用:)
Timer _timer;
void _schedule() {
_timer = Timer(Duration(seconds: 2), () {
print('Do something after delay');
});
}
@override
void dispose() {
super.dispose();
_timer?.cancel();
}
据我所知,没有办法取消未来。但是有一种方法可以取消 Stream 订阅,也许这可以帮助您。
调用onSubmit
按钮会返回一个StreamSubscription
对象。您可以显式存储该对象,然后调用cancel()
它来取消流订阅:
StreamSubscription subscription = someDOMElement.onSubmit.listen((data) {
// you code here
if (someCondition == true) {
subscription.cancel();
}
});
稍后,作为对某些用户操作的响应,您也许可以取消订阅:
我完成“取消”计划执行的一种方法是使用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
才会执行。
对于那些试图在 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);
}
}
我的 2 美分值...
class CancelableFuture {
bool cancelled = false;
CancelableFuture(Duration duration, void Function() callback) {
Future<void>.delayed(duration, () {
if (!cancelled) {
callback();
}
});
}
void cancel() {
cancelled = true;
}
}
将未来的任务从“做某事”改为“做某事,除非它被取消”。实现这一点的一个明显方法是设置一个布尔标志并在开始处理之前的未来关闭中检查它,也许在处理过程中的几个点。
此外,这似乎有点小技巧,但是将未来的超时设置为零似乎可以有效地取消未来。
下面的代码有助于设计未来的超时功能,可以手动取消。
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
对象也应该相应地改变。
这是取消可等待的延迟未来的解决方案
这个解决方案就像awaitableTimer
或cancelableFuture.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中提供了一个具有更完整类的运行示例。
一个从未来取消注册回调的小类。此类不会阻止执行,但可以在您需要切换到具有相同类型的另一个未来时提供帮助。不幸的是我没有测试它,但是:
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();
});
}
您可以在以下情况下使用它,当语言发生变化并提出请求时,您不关心先前的响应并提出另一个请求!此外,我真的很想知道这个功能不是来自盒子。