Уборка
Задача может взять блокировку, открыть базу данных или создать временный файл, а её дети могут пользоваться ими и после возврата из тела. Регистрируйте освобождение в контексте, в месте получения ресурса, и задача выполнит его, когда завершится целиком:
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());
return database;| Что задача держит | Член | Когда выполняется |
|---|---|---|
| То, что она оставляет себе, из вызова: блокировка, временный файл, подписка | dispose у abandonable, join или run |
при любом исходе |
| То, что тело возвращает или отдаёт наружу, из вызова | discard у abandonable, join или run |
если задача кончилась не Done |
| То, что она оставляет себе, когда оборачивать нечего | ctx.onDispose |
при любом исходе |
| То, что тело возвращает или отдаёт наружу, когда оборачивать нечего | ctx.onDiscard |
если задача кончилась не Done |
Значение из abandonable, join или run, которое переходит в другие руки |
ctx.disown(value) |
никогда: снимает регистрацию, которую сделал вызов |
Если передать в один вызов и dispose, и discard, будет ArgumentError.
Эта страница выбирает между dispose и discard и закрывает значение, которое
задача не отдаёт. «Правила уборки» продолжают отсюда:
ресурс, который передают из задачи в задачу, ctx.onDispose и ctx.onDiscard,
когда оборачивать нечего, порядок уборки и значение, которое приходит поздно.
Строки под кодом ниже — то, что он печатает при запуске. Блокировка и база
сообщают, что с ними происходит, а outcome: — то, чем задача кончилась.
Выбор колбэка
Заголовок раздела «Выбор колбэка»Задача берёт блокировку для своей работы и открывает базу, которую возвращает тому, кто её попросил. Блокировка нужна работе задачи, база нужна вызывающему, если до него дойдёт; ни то, ни другое не должно жить дольше, чем нужно. Задача завершается успешно.
Первая попытка
Заголовок раздела «Первая попытка»Оба ресурса уходят вместе с одной задачей, поэтому один и тот же колбэк выглядит подходящим для обоих:
// Первая попытка: при успехе блокировка не освобождается.final lock = await ctx.join(Lock.acquire, discard: (lock) => lock.release());final database = await ctx.join( Database.open, discard: (database) => database.close(),);
return database;lock acquireddatabase openedoutcome: Done(database)Блокировка так и не освобождается. Вот что происходит:
discardвыполняется, только когда задача кончилась неDone.- Задача, которая возвращает базу, кончается
Done(database), поэтому ни одинdiscardне выполняется. - База остаётся открытой для вызывающего, как и должна, а блокировка остаётся захваченной на всё время жизни процесса.
Отменённая или упавшая задача освобождает и то, и другое; блокировка утекает только на том пути, ради которого задача и написана. Тест, который отменяет задачу, ничего не заметит: на этом пути колбэк как раз выполняется.
dispose для того, что задача оставляет себе
Заголовок раздела «dispose для того, что задача оставляет себе»final lock = await ctx.join(Lock.acquire, dispose: (lock) => lock.release());final database = await ctx.join( Database.open, discard: (database) => database.close(),);
return database;lock acquireddatabase openedlock releasedoutcome: Done(database)| Ресурс | Колбэк | Задача завершилась успешно | Задача упала или отменена |
|---|---|---|---|
| То, что задача оставляет себе: блокировка, временный файл, подписка | dispose |
освобождается | освобождается |
| То, что тело возвращает или отдаёт наружу | discard |
передаётся открытым | освобождается |
Ветка ctx.runAll освобождает позже, чем кончается её тело, поэтому
блокировка, которую делят две ветки, может подвесить группу. Как выглядит
зависание и как брать такую блокировку, показывает раздел
«Ветки, которые ждут друг друга»
страницы о группах.
Каждый колбэк ожидается, поэтому задача кончается не раньше последнего из них.
Кто ждёт задачу, ждёт и их: контроллер solo завершает close() только после
уборки задачи, которую отменил.
Задача держит зарегистрированные колбэки на своём стеке уборки. Они выполняются
после конца тела, и отмена их не прерывает: ни та, что уже принята, ни та, что
придёт в саму размотку. check, abandonable, join, pause,
uncancellable, run, start, runAll, each и onCancel принадлежат телу
и здесь бросают StateError, поэтому колбэк должен ждать свой ресурс напрямую.
В отменённой задаче check бросает вместо него её Cancelled, как и после
конца задачи. Поэтому брошенное телом действие, которое опрашивает check,
чтобы остановиться, получает ту отмену, которой ждёт.
Два значения освобождаются, не дожидаясь стека: то, на котором бросает контрольная точка в момент его прихода, и то, которое приходит поздно. Оба случая — на странице о правилах уборки, в разделах «Регистрация по приходу» и «Значение, которое приходит поздно».
Ждать свою же задачу колбэк не должен: done, value и cancel() завершаются
после той уборки, которая их и ждала бы. Держите колбэки короткими и не давайте
им ждать ничего, кроме самого освобождения: прервать их нечему. Ошибка одного
из них уходит в onError и по умолчанию дальше
в зону, а остальные колбэки всё равно
выполняются; Cancelled останавливается на onError.
Значение, которое задача не отдаёт
Заголовок раздела «Значение, которое задача не отдаёт»Задача открывает базу для вызывающего и отдаёт её, только если схема актуальна.
Иначе она возвращает null, и вызывающий сначала делает миграцию. В этот раз
схема устарела.
Первая попытка
Заголовок раздела «Первая попытка»// Первая попытка: база, которую задача не отдаёт, остаётся открытой.final database = await ctx.join( Database.open, discard: (database) => database.close(),);
if (!await ctx.join(database.isCurrent)) { return null;}
return database;database openedoutcome: Done(null)База так и не закрывается, и её никто не держит. Вот что происходит:
discardвыполняется, только когда задача кончилась неDone.return nullкончает задачуDone(null).discardсмотрит, чем кончилась задача, а не куда ушла база, поэтому не выполняется.
Отладочный канал, который включается строкой Job.debug = print;, называет
значение, без которого задача кончилась:
Job() ended Done(null) without the value of 1 conditional cleanup (database): unless it is inside the result, nothing will dispose of itЗакрыть то, что задача не отдаёт
Заголовок раздела «Закрыть то, что задача не отдаёт»final database = await ctx.join( Database.open, discard: (database) => database.close(),);
if (!await ctx.join(database.isCurrent)) { ctx.disown(database); await database.close(); return null;}
return database;database openeddatabase closedoutcome: Done(null)Тело, которое не отдаёт значение, закрывает его само, а регистрацию сначала
забирает назад. Пока база закрывается, может прийти отмена и кончить задачу
Cancelled; без ctx.disown discard тогда закрыл бы базу второй раз.
ctx.disown ищет значение по идентичности и возвращает, нашёл ли его, поэтому
передавайте ему ровно тот объект, который вернул вызов.