dartdart-async

Capture all futures in a Zone


In Dart is there any way to capture all Future's created inside a Zone?

Currently, I'm forced to manually capture them all, but this seems prone to forgetting to do it. So I want to automate this if possible.

In the example code below I want to capture someAsyncWork so I can await it after the main runPluginCode has run.

import 'dart:async';

Future<void> main() async {
  print('Run plugin code');
  await runZoned(
    () async {
      runPluginCode();

      final tasks = Zone.current['tasks'] as List<Future>;

      await Future.wait(tasks);
    },
    zoneValues: {
      'taskId': 123,
      'tasks': <Future>[],
    },
  );
  print('Exit plugin code');
}

void runPluginCode() {
  // External QuickJS script that only has sync API methods
  // but they may call into async functions that need to be awaited before
  // the plugin code ends
  someTask();
}

void someTask() {
  print('performing task');
  // I don't want manually do this everywhere
  // final tasks = Zone.current['tasks'] as List<Future>;
  // tasks.add(someAsyncWork());
  someAsyncWork();
}

Future<void> someAsyncWork() async {
  await Future.delayed(const Duration(milliseconds: 200));
  final taskId = Zone.current['taskId'] as int;
  print('async work completed for task $taskId did I complete inside the plugin code???');
}

I believe this should be possible when using a custom ZoneSpecification

I tried this but without any luck.

    zoneSpecification: ZoneSpecification(
      run: <R>(self, parent, zone, f) {
        final task = parent.run(zone, f);
        if (task is Future) {
          (zone['tasks'] as List<Future>).add(task);
        }
        return task;
      },
      runUnary: <R, T>(self, parent, zone, f, arg) {
        final task = parent.runUnary(zone, f, arg);
        if (task is Future) {
          (zone['tasks'] as List<Future>).add(task);
        }
        return task;
      },
      runBinary: <R, T1, T2>(self, parent, zone, f, arg1, arg2) {
        final task = parent.runBinary(zone, f, arg1, arg2);
        if (task is Future) {
          (zone['tasks'] as List<Future>).add(task);
        }
        return task;
      },
    )

Solution

  • No. There is no way to capture all futures, or any futures, created inside a Zone.

    Zones are a lower-level abstraction than futures. Futures are built on top of zones, so zones do not know anything about futures, or streams.

    You can respond to anyone calling then on the future, because that calls zone.registerUnaryCallback, but just creating the future is a simple operation that doesn't do any zone-related operations. It captures the current zone (which it really shouldn't need to do), but that is it.

    If you want to make sure that all computation inside the zone has completed, then ... well, you probably cannot, but you can get closer by:

    It's generally impossible to know whether a zone will ever have computations happen in it again, until the zone gets garbage collected. Someone could have stored the zone in a global variable, to later call storedZone.run(() { any code here });