есть ли способ отменить будущее дротика?

В пользовательском интерфейсе Dart у меня есть кнопка [отправить] для запуска длинного асинхронного запроса. Обработчик [submit] возвращает Future. Затем кнопка [отправить] заменяется кнопкой [отменить], чтобы разрешить отмену всей операции. В обработчике [cancel] я хотел бы отменить длинную операцию. Как я могу отменить будущее, возвращенное обработчиком отправки? Я не нашел способа сделать это.


person Serge Tahé    schedule 09.07.2013    source источник


Ответы (10)


Насколько я знаю, будущее невозможно отменить. Но есть способ отменить подписку на Stream, и, возможно, это поможет вам.

Вызов onSubmit на кнопке возвращает объект StreamSubscription. Вы можете явно сохранить этот объект, а затем вызвать для него cancel(), чтобы отменить подписку на поток:

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

   // you code here

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

Позже, в ответ на какое-то действие пользователя, возможно, вы сможете отменить подписку:

person Shailen Tuli    schedule 11.07.2013
comment
Я последовал твоей идее. Это сработало. Дело было в следующем. Уровень DAO возвратил 1000 случайных чисел, каждое случайное число генерировалось удаленным сервером через 1000 HTTP-запросов. Уровень DAO возвратил ответ, включающий 2 списка: список из 1000 Future ‹int› и список из 1000 StreamSubscription, связанных с 1000 HTTP-запросами. Я использовал фьючерсы для отображения случайных чисел в пользовательском интерфейсе и StreamSubscriptions для отмены HTTP-запросов. Спасибо ! - person Serge Tahé; 15.07.2013
comment
Кроме того, вам действительно нужна 1000 HTTP-запросов? По какой причине вы не можете просто сгенерировать 1000 случайных чисел с помощью Math.Random? Или у вас есть только один HTTP-запрос, который дает вам все числа? - person Shailen Tuli; 15.07.2013
comment
Я просто хотел проверить отмену списка Future. Вот как я проводил этот тест. Да, я принимаю ответ. - person Serge Tahé; 15.07.2013

Вы можете использовать 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.

Суть кода

person vovahost    schedule 27.02.2019
comment
Престижность за использование стандартных библиотек дротиков вместо изобретения собственного колеса. - person Alex Semeniuk; 07.08.2019
comment
Однако это не стандартная библиотека для дротиков. Это внешняя зависимость. - person Andrey Gordeev; 23.04.2020
comment
хороший момент об асинхронности. Это очень сбивает с толку, потому что есть собственный пакет async. - person Soufiane Ghzal; 02.05.2020
comment
Обратите внимание, что это не работает с отложенными фьючерсами. Они основаны на Timer, и ссылка на внутренний таймер не сохраняется, поэтому невозможно предотвратить выполнение обратного вызова после его начала. - person geg; 31.10.2020
comment
этот ответ вводит в заблуждение. эти классы не отменяют само будущее, а создают оболочки, которые вы можете использовать для имитации поведения отмены. - person nt4f04und; 07.04.2021

Как отменить Future.delayed

Простой способ - использовать вместо этого Timer :)

Timer _timer;

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

@override
void dispose() {
  super.dispose();
  _timer?.cancel();
}
person Andrey Gordeev    schedule 23.04.2020
comment
Но вы не можете await на Timer :( - person iDecode; 08.10.2020
comment
Как вы вызываете cancel _timer при касании детектора жестов? - person Texv; 17.01.2021
comment
@Texv _timer?.cancel(); - person Andrey Gordeev; 19.01.2021
comment
@AndreyGordeev, да ладно, я еще кое-что задумал. _timer? .cancel () отменяет работу функции по истечении таймера продолжительности. Вместо этого я хотел отменить продолжительность (перейти на 0 секунд), чтобы выполнить функцию быстрее - person Texv; 20.01.2021
comment
@iDecode Вы можете указать Timer заполнить Completer, а вызывающие абоненты могут await Completer Future. - person jamesdlin; 08.02.2021
comment
@jamesdlin О боже, большое спасибо, сэр, что поделился этим. Ты всегда доказываешь (так или иначе), что ты лучший !!! - person iDecode; 08.02.2021
comment
Хороший способ решить проблему. Это не совсем отменяет будущее, но делает то, что мне нужно для дела - person Leo; 03.06.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);
  }
}
person CopsOnRoad    schedule 29.05.2019

Один из способов «отменить» запланированное выполнение - использовать 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.

person Feu    schedule 06.12.2018
comment
ОП просил об отмене будущего, а не казни. - person Jon Scalet; 13.02.2020

мои 2 цента стоят ...

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

  void cancel() {
    cancelled = true;
  }
}
person Robert Sutton    schedule 14.08.2019
comment
Взгляните на CancelableOperation в принятом ответе. - person Jon Scalet; 13.02.2020
comment
@JonScalet Я не думаю, что CancelableOperation предотвращает выполнение будущего обратного вызова, тогда как ответ делает. Попробуйте отменить Future.delayed(...), и он все равно будет выполняться --- CancelableOperation.fromFuture(Future.delayed(Duration(seconds: 1), () => print("Finished 1")), onCancel: () => print("Cancelled"),).cancel() - person geg; 31.10.2020
comment
@geg Вы правы, это фактически не отменяет вычисление / выполнение Будущего. - person Jon Scalet; 16.11.2020

В async package на pub.dev, который вы можете использовать для этого сейчас. Этот пакет не следует путать со встроенной базовой библиотекой dart dart:async, в которой нет этого класса.

person ThinkDigital    schedule 17.11.2020

Измените задачу будущего с «сделать что-то» на «сделать что-то, если оно не было отменено». Очевидный способ реализовать это - установить логический флаг и проверить его при закрытии в будущем, прежде чем приступить к обработке, и, возможно, в нескольких точках во время обработки.

Кроме того, это кажется чем-то вроде взлома, но установка тайм-аута будущего на ноль, по-видимому, фактически отменяет будущее.

person Argenti Apparatus    schedule 04.08.2014
comment
Кажется, установка тайм-аута будущего на ноль не отменяет будущее. Ниже приведен код и результаты, подтверждающие это. 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 объект также должен быть изменен соответственно.

person Murali Krishna Regandla    schedule 21.08.2020

Небольшой класс для отмены регистрации обратных вызовов из будущего. Этот класс не предотвратит выполнение, но может помочь, когда вам нужно переключиться на другое будущее с тем же типом. К сожалению, не тестировал, но:

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();
    });
  }

Вы можете использовать его в ситуациях, когда язык изменился и запрос был сделан, вас не волнует предыдущий ответ и отправка другого запроса! Кроме того, меня действительно удивило, что эта функция не была «из коробки».

person Atamyrat Babayev    schedule 08.07.2021