unattended method

  1. @override
void unattended(
  1. FutureOr<void> action()
)
override

Runs action as work this job does not wait for.

Comes back at once and hands the work to the core: the job neither waits for it nor cancels it, and whatever action leaves uncaught — now, or long after the job is over — goes to the job's observer and its answer, never to the body. Legal on a job that has accepted a cancellation and while the core unwinds the cleanup stack; once the job has finished it throws a StateError. run, runAll, each and uncancellable throw a StateError in here at any time: this work is not the job. Start the work in here and take nothing out of it: the work runs in an error zone of its own, and the void return does not stop a closure carrying a future out.

ctx.unattended(() => analytics.report(event));

Unfinished work holds the job, its outcome, everything the work captured — and whatever an engine hangs on the job: in solo, for example, the controller itself, after close() too. The body of a finished job is let go of; what the work captured from it is not. A periodic timer left running in here leaks all of that. Work that must end with the job puts its own stop on the cleanup stack, ctx.onDispose(timer.cancel), or takes the cancellation through onCancel; polling check is no stop.

See Work the job does not wait for on the errors page.

Implementation

@override
void unattended(FutureOr<void> Function() action) {
  // First, and by name: a context outliving its job is the mistake this
  // member is likeliest to be caught in, and the message has to say
  // which call threw. The cleanup window stays open on purpose — a
  // disposer starting work nobody waits for is what this is for.
  if (_owner.isFinished) {
    throw StateError(
      '$_owner has already finished, cannot start unattended work',
    );
  }
  // The zone a job born in this work reports to. Taken from the fork
  // around us when there is one, so nesting does not walk the address
  // one fork outwards on every level: the answer is the zone the body
  // itself runs in, however deep the call is.
  final from = (Zone.current[_unattendedKey] as Zone?) ?? Zone.current;
  runZonedGuarded<void>(
    () {
      action();
    },
    _unattendedError,
    // Two values, and the key of the second is the job's own: forks of
    // different jobs nest, and one shared key would let the inner one
    // hide the outer. Under a key of its own each job sees its own work
    // and nobody else's.
    //
    // Not the job itself as the key. A zone looks a key up by `==`, and a
    // job of a domain may well compare itself by a key of its own: of two
    // such jobs with forks nested, the inner fork would answer for the
    // outer job too, and work of one would pass for work of the other.
    zoneValues: {_unattendedKey: from, _owner._forkKey: _owner},
  );
}