How to make a delayed future cancelable in Dart?
Asked Answered
H

3

5

Lets say that in Dart/Flutter you have the following code:

void myOperation() {
  // could be anything
  print('you didn't cancel me!');
}

Notice that the operation itself is not asynchronous and is void -- does not return anything.

We want it to execute at some point in the future, but we also want to be able to cancel it (because a new operation has been requested that supersedes it).

I've started by doing this:

Future.delayed(Duration(seconds: 2), myOperation())

... but this is not cancellable.

How exactly could you schedule that "operation," but also make it cancelable?

I'm thinking... we could modify the code like so:

Future.delayed(Duration(seconds: 2), () {
    if (youStillWantThisToExecute) {
        print('you didn't cancel me!');
    }
});

But that's not really very good because it depends on a "global" boolean... and so if the boolean gets flipped to false, no operations will complete, even the most recently requested, which is the one we want to complete.

It would be nicer if there were a way to create any number of instances of the operation and cancel them on an individual basis... or to have a unique id assigned to each operation, and then instead of having a boolean control whether or not to execute... to have a "mostRecentId" int or something which is checked prior to execution.

Anyways...

CancelableOperation seemed promising just from its name.

So, I looked at its documentation:

CancelableOperation.fromFuture(Future inner, {FutureOr onCancel()}) Creates a CancelableOperation wrapping inner. [...] factory

But honestly that just makes my poor head hurt oh so much.

I've consulted other articles, questions, and answers, but they are all part of some specific (and complex) context and there isn't a dirt simple example anywhere to be found.

Is there a way to make a delayed future cancellable by wrapping it in some other class?

Can someone more experienced please provide at least one simple, complete, verified example that compiles in DartPad?

Thanks.

Hellenism answered 26/2, 2021 at 5:29 Comment(3)
Are you open to other suggestions not using CancelableOperation?Aholah
@Aholah I'd like to know thatHimelman
Does this answer your question? How do I cancel Future.delayed?Defend
M
1

You cannot cancel an existing Future. If you do:

Future.delayed(
  Duration(seconds: 2),
  () {
    print('hello');
  },
);

as long as the process runs (and is processing its event queue) for at least 2 seconds, the Future eventually will execute and print 'hello'.

At best you can cause one of the Future's completion callbacks to fire prematurely so that callers can treat the operation as cancelled or failed, which is what CancelableOperation, et al. do.

Edit:

Based on your updated question, which now asks specifically about delayed Futures, you instead should consider using a Timer, which is cancelable. (However, unlike a Future, callers cannot directly wait on a Timer. If that matters to you, you would need to create a Completer, have callers wait on the Completer's Future, and let the Timer's callback complete it.)

Motherwort answered 28/2, 2021 at 6:45 Comment(0)
N
4

Use Timer:

var t = Timer(Duration(seconds: 400), () async {
   client.close(force: true);
});
...
t.cancel();
Nostoc answered 2/3, 2021 at 6:24 Comment(0)
A
2

Using CancalableOperation will not stop print('hello'); from executing even if you cancel. What it does is canceling(discarding) the result(void in your case). I will give you 2 examples using CancalableOperation and CancalableFuture.

CancelableOperation example

    final delayedFuture = Future.delayed(
      Duration(seconds: 2),
      () {
        return 'hello';
      },
    );

    final cancellableOperation = CancelableOperation.fromFuture(
      delayedFuture,
      onCancel: () => {print('onCancel')},
    );

    cancellableOperation.value.then((value) => {
          // Handle the future completion here
          print('then: $value'),
        });
    cancellableOperation.value.whenComplete(() => {
          print('onDone'),
        });
    cancellableOperation.cancel(); // <- commment this if you want to complete

CancelableFuture example

    final delayedFuture = ...;

    final cancalableFuture = CancelableFuture<String>(
      future: delayedFuture,
      onComplete: (result) {
        // Use the result from the future to do stuff
        print(result);
      },
    );
    cancalableFuture.cancel(); // <- commment this if you want to complete

And the CancelableFuture implementation

class CancelableFuture<T> {
  bool _cancelled = false;
  CancelableFuture({
    @required Future<dynamic> future,
    @required void Function(T) onComplete,
  }) {
    future.then((value) {
      if (!_cancelled) onComplete(value);
    });
  }
  void cancel() {
    _cancelled = true;
  }
}
Aholah answered 26/2, 2021 at 6:29 Comment(3)
So I see how to cancel the CancelableFuture using .cancel(), but how do to start/run it in the first place? cancelableFuture.run()? no.cancelableFuture()? no. :SHellenism
You run the future here: Future.delayed(. You are just passing the Future and if you cancel, the onComplete will not fire. Thats it. Maybe I did not get the question right. What exactly are you trying to do with a Future that requires canceling? Can you give more info so we can try to come up with a solution.Aholah
No you got it right I'm sure. The question isn't that great. I'll edit it a little bit. I'm confused mainly because I'm just trying to put it all together in dartpad so I understand it, but I'm getting errors that I don't understand.Hellenism
M
1

You cannot cancel an existing Future. If you do:

Future.delayed(
  Duration(seconds: 2),
  () {
    print('hello');
  },
);

as long as the process runs (and is processing its event queue) for at least 2 seconds, the Future eventually will execute and print 'hello'.

At best you can cause one of the Future's completion callbacks to fire prematurely so that callers can treat the operation as cancelled or failed, which is what CancelableOperation, et al. do.

Edit:

Based on your updated question, which now asks specifically about delayed Futures, you instead should consider using a Timer, which is cancelable. (However, unlike a Future, callers cannot directly wait on a Timer. If that matters to you, you would need to create a Completer, have callers wait on the Completer's Future, and let the Timer's callback complete it.)

Motherwort answered 28/2, 2021 at 6:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.