Перейти к содержимому

Уборка

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

final lock = await ctx.join(Lock.acquire, dispose: (lock) => lock.release());
final database = await ctx.join(
Database.open,
discard: (database) => database.close(),
);
await ctx.join(() => database.migrate(stop));
return database;

Выбор колбэка зависит от того, кому нужен ресурс после успешного завершения:

  • dispose выполняется при любом исходе. Используйте его для ресурсов, которые нужны только задаче, например блокировки или временного файла.
  • discard выполняется при отмене или ошибке. Используйте его для значений, которые тело возвращает или передаёт вызывающему коду. В этом примере успешная задача оставляет базу открытой для вызывающего, а в остальных случаях закрывает её.

Если использовать discard для временного ресурса, который тело оставляет себе, при успешном завершении возникнет утечка: колбэк не выполнится.

Если вызова получения ресурса нет, зарегистрируйте колбэк напрямую через ctx.onDispose или ctx.onDiscard. Правила исходов для них те же. Колбэк может выполнять и другую завершающую работу, например отправить накопленное содержимое буфера при завершении задачи:

final buffer = StringBuffer();
ctx.onDispose(() => sink.add(buffer.toString()));

Оба метода возвращают функцию снятия регистрации. Используйте её, если ресурс уже освобождён или передан другому владельцу. Повторный вызов, в том числе после выполнения уборки, безопасен.

Если операция сама освобождает ресурс, снимайте регистрацию внутри того же действия. Иначе при отмене join может бросить исключение до того, как тело дойдёт до снятия регистрации, и колбэк уборки останется в списке:

final removeDisposer = ctx.onDispose(cursor.close);
await ctx.join(() async {
await cursor.readAll(); // в конце закроет сам
removeDisposer();
});

wait и join возвращают ресурс, поэтому функции снятия регистрации у тела нет. Для этих регистраций используйте ctx.disown(value), если сами передаёте ресурс другому владельцу. Метод снимает регистрацию по идентичности объекта и возвращает, нашёл ли её. Передайте тот же экземпляр, который вернула операция.

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

Колбэки уборки работают после окончания тела и не отменяются. ctx.wait и ctx.join здесь недоступны, поэтому освобождения ресурсов внутри колбэка ждут напрямую. Колбэк не должен ждать собственную задачу: done, value и cancel() ждут окончания уборки, поэтому такое ожидание приведёт к взаимной блокировке. Делайте колбэки короткими и безусловными. Ошибки обрабатываются по правилам наблюдателя, описанным ниже, а остальные колбэки продолжают выполняться.

Отмена после возврата из тела. После return задача ещё может ждать дочерние задачи или выполнять уборку. Отмена в это время может изменить исход на Cancelled. База, зарегистрированная через discard, тогда будет закрыта вместо передачи вызывающему коду. Если discard уже был пропущен на успешном пути, он выполнится вторым проходом. Поэтому он может выполниться после колбэков, зарегистрированных раньше него.

Значение, вернувшееся из действия, ожидание которого прервал wait, тоже требует уборки независимо от исхода: тело его не получило. Зарегистрированный колбэк освобождения выполнится, даже если задача уже завершилась.

Следующий пример показывает, что регистрация уборки работает и после обычного await. Для обычного получения ресурса используйте ctx.join с discard, как в примере выше:

final job = Job<Database>((ctx) async {
final database = await Database.open();
ctx.onDiscard(database.close);
return database;
});

Здесь отмена не прерывает Database.open(). Тело продолжает ждать, а затем регистрирует открытую базу. Если во время открытия задачу отменили, итоговым исходом будет Cancelled, и onDiscard закроет базу.