В пользовательском интерфейсе Dart у меня есть кнопка [отправить] для запуска длинного асинхронного запроса. Обработчик [submit] возвращает Future. Затем кнопка [отправить] заменяется кнопкой [отменить], чтобы разрешить отмену всей операции. В обработчике [cancel] я хотел бы отменить длинную операцию. Как я могу отменить будущее, возвращенное обработчиком отправки? Я не нашел способа сделать это.
есть ли способ отменить будущее дротика?
Ответы (10)
Насколько я знаю, будущее невозможно отменить. Но есть способ отменить подписку на Stream, и, возможно, это поможет вам.
Вызов onSubmit
на кнопке возвращает объект StreamSubscription
. Вы можете явно сохранить этот объект, а затем вызвать для него cancel()
, чтобы отменить подписку на поток:
StreamSubscription subscription = someDOMElement.onSubmit.listen((data) {
// you code here
if (someCondition == true) {
subscription.cancel();
}
});
Позже, в ответ на какое-то действие пользователя, возможно, вы сможете отменить подписку:
Вы можете использовать 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
или await completer.operation
, то future никогда не вернет результат и будет ждать бесконечно, если операция была отменена. Это потому, что await cancellableOperation.value
то же самое, что и запись cancellableOperation.value.then(...)
, но then()
никогда не будет вызываться, если операция была отменена.
Не забудьте добавить пакет async Dart.
Timer
, и ссылка на внутренний таймер не сохраняется, поэтому невозможно предотвратить выполнение обратного вызова после его начала.
- person geg; 31.10.2020
Как отменить Future.delayed
Простой способ - использовать вместо этого Timer
:)
Timer _timer;
void _schedule() {
_timer = Timer(Duration(seconds: 2), () {
print('Do something after delay');
});
}
@override
void dispose() {
super.dispose();
_timer?.cancel();
}
await
на Timer
:(
- person iDecode; 08.10.2020
_timer?.cancel();
- person Andrey Gordeev; 19.01.2021
Timer
заполнить Completer
, а вызывающие абоненты могут await
Completer
Future
.
- person jamesdlin; 08.02.2021
Для тех, кто пытается добиться этого во 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);
}
}
Один из способов «отменить» запланированное выполнение - использовать 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
.
мои 2 цента стоят ...
class CancelableFuture {
bool cancelled = false;
CancelableFuture(Duration duration, void Function() callback) {
Future<void>.delayed(duration, () {
if (!cancelled) {
callback();
}
});
}
void cancel() {
cancelled = true;
}
}
CancelableOperation
предотвращает выполнение будущего обратного вызова, тогда как ответ делает. Попробуйте отменить Future.delayed(...)
, и он все равно будет выполняться --- CancelableOperation.fromFuture(Future.delayed(Duration(seconds: 1), () => print("Finished 1")), onCancel: () => print("Cancelled"),).cancel()
- person geg; 31.10.2020
В async package на pub.dev, который вы можете использовать для этого сейчас. Этот пакет не следует путать со встроенной базовой библиотекой dart dart:async
, в которой нет этого класса.
Измените задачу будущего с «сделать что-то» на «сделать что-то, если оно не было отменено». Очевидный способ реализовать это - установить логический флаг и проверить его при закрытии в будущем, прежде чем приступить к обработке, и, возможно, в нескольких точках во время обработки.
Кроме того, это кажется чем-то вроде взлома, но установка тайм-аута будущего на ноль, по-видимому, фактически отменяет будущее.
Future f = new Future.delayed(new Duration(seconds: 2), () => print('2 secs passed')); f.timeout(const Duration(seconds: 0), onTimeout: () { print ('timed out'); });
печатает timed out 2 secs passed
. Перехват увеличенного тайм-аута с помощью catchError или предоставление параметра onTimeout не имеет значения. Будущее всегда в бегах.
- person Gazihan Alankus; 15.02.2015
Следующий код помогает спроектировать будущую функцию, которая истекает по таймауту и может быть отменена вручную.
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
объект также должен быть изменен соответственно.
Небольшой класс для отмены регистрации обратных вызовов из будущего. Этот класс не предотвратит выполнение, но может помочь, когда вам нужно переключиться на другое будущее с тем же типом. К сожалению, не тестировал, но:
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;
}
}
А вот и пример использования. P.S. Я использую в своем проекте провайдера:
_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();
});
}
Вы можете использовать его в ситуациях, когда язык изменился и запрос был сделан, вас не волнует предыдущий ответ и отправка другого запроса! Кроме того, меня действительно удивило, что эта функция не была «из коробки».