Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

There is no way to cancel a function if we realize the work is no longer required #59903

Closed
stephane-archer opened this issue Jan 14, 2025 · 7 comments
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. type-enhancement A request for a change that isn't a bug

Comments

@stephane-archer
Copy link

There is no way to cancel a function if we realize the work is no longer required:

Future<String> expensiveFunction() async {
await Future.delay(Duration(minutes: 10));
print("hello world!");
}

void main() async {
   var future = expensiveFunction();
   //Some events came
   future.cancel();
}

Right now from my understanding: expensiveFunction will compute on my CPU for 10 minutes even if I know I don't need this value anymore and I will see "hello world!" on the console no matter what I do.

@dart-github-bot
Copy link
Collaborator

Summary: Futures in Dart lack a cancellation mechanism. expensiveFunction continues execution even after attempting future.cancel().

@dart-github-bot dart-github-bot added area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. type-enhancement A request for a change that isn't a bug labels Jan 14, 2025
@julemand101
Copy link
Contributor

You need to manually implement a concept of cancellation since there are no link from a given Future to the logic that might fill out the value later on (e.g. if you use a Completer, it could be filled by any code). It is also not without issues just stop running a method without a way to define where it should stop safely and how to stop safely.

If you look in the package:async, you can find an attempt to define some kind of alternative protocol to have operations that can be canceled:
https://pub.dev/documentation/async/latest/async/CancelableOperation-class.html

@stephane-archer
Copy link
Author

@julemand101 from my understanding the package:async only defines a nice interface that pretends things are cancelable, but behind the scenes, nothing gets canceled. print("hello world!") will be executed and resources will be used on the user device.

That's not what this issue is about, it is about canceling the work behind a future.

@julemand101
Copy link
Contributor

@stephane-archer
As I also wrote, you need to manually add the handling of cancellation and the package will just help you do that.

I also write why it is not feasible to have this handled automatically since we should expect any function in Dart to run to completion unless the program are terminated by force or exception are thrown. And, as also described, we don't know which function are going to complete a given Future.

Yes, in this case, with a Future created from an async marked function, we could kinda trace the original function and then do something specific here. But that would not work for all Future objects. And it would also be hard to define what "cancel" means since your function awaits something else. And we then need to make sure we can here also identify which function are completing this function and so on....

And since Future objects can be created by other than async-marked functions, (e.g. Completer), it is going to be near impossible to get a full tree of code that we can then just "cancel" by just not run this code. This automatic "cancel" could also be very problematic if you need to tell some driver that you want to close a resource as part of the "cancel".

The overall design of Future in Dart and the event driven runtime model related to this, makes it hard/impossible to make an overall "cancel" design that would just work. You would, regardless, need to manually handle this even if you could ask a Future if it had been cancelled from the other end.

And CancelableOperation are a way to enforce this logic into your design.

@mraleph
Copy link
Member

mraleph commented Jan 14, 2025

Duplicate of #42855

@mraleph mraleph marked this as a duplicate of #42855 Jan 14, 2025
@mraleph mraleph closed this as completed Jan 14, 2025
@stephane-archer
Copy link
Author

@julemand101 could you clarify how I can make expensiveFunction cancellable, what type of code should I add to my function or package? If this can not be done automatically, can this be done manually?

@julemand101
Copy link
Contributor

@stephane-archer
There are no easy solution for your code since you are using Future.delayed which does not have a concept of cancelling the waiting for the inner timer event that gets created when make the Future. So best we can do is the following which sucks since the Future still prevents our program from stopping:

import 'package:async/async.dart';

CancelableOperation<String> expensiveFunction() {
  CancelableCompleter<String> completer = CancelableCompleter();

  () async {
    await Future<void>.delayed(Duration(minutes: 10));

    if (!completer.isCanceled) {
      print("hello world!");
    }
  }();

  return completer.operation;
}

void main() async {
  var future = expensiveFunction();
  //Some events came
  future.cancel();
}

If we instead redesign this a bit so we get access to the timer, we can be a bit more elegant:

CancelableOperation<String> expensiveFunction() {
  Timer? timer;
  CancelableCompleter<String> completer = CancelableCompleter(onCancel: () {
    timer?.cancel();
  });

  timer = Timer(Duration(minutes: 10), () {
    print("hello world!");
  });

  return completer.operation;
}

void main() async {
  var future = expensiveFunction();
  //Some events came
  future.cancel();
}

But this also shows that it can be hard to implement a design that supports cancel of operations since you might end up implement a lot of code to support this correctly. And if you do any async calls that does not support cancel, then you are forced to wait at some point.

An alternative is to use isolate's which supports immediate killing. But that does still then come with the problem of missing cleanup and, as mention before, it can be problematic in some scenarios to just stop executing some code while it is running.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

4 participants