Список изменений
Не выпущено
Заголовок раздела «Не выпущено»Документация
Заголовок раздела «Документация»- README стал коротким входом с разделом о
ctx.abandonable: «Зачем» и «Развёрнутый пример» переехали на «Зачем задача», а таблица контекста и «Четыре слова» — в «Справочник». - README называет в разделе «Установка» плагин анализатора
async_job_lintsс правилами для кода наasync_job; код наsoloназываетsolo_lints, как говорит его раздел «solo». - «Срок для одного шага» на странице о времени первым в ветке, которая ловит
срок ребёнка, вызывает
ctx.check(). Без него задача, у которой собственныйtimeoutвышел доctx.run, принимала свойCancelled(timeout)за срок ребёнка и шла дальше: причина срока не говорит, чей он.
Ломающие изменения
Заголовок раздела «Ломающие изменения»ctx.wait, устаревший с 0.3.0 в пользуctx.abandonable, удалён.HandlerCancelReasonпереименован вBodyCancelReason, а егоname, который печатают исходы и трассы, теперьbody, а неhandler. Эта причина говорит, что тело отменило себя само, прямо или выпустив отмену дочерней задачи.
Добавлено
Заголовок раздела «Добавлено»Job.debugпечатаетawaited before start, когдаdoneилиvalueуJob.deferredвпервые читают, пока её никто не запустил: ожидание такой задачи не кончится, пока её не запустят или не отменят.
Документация
Заголовок раздела «Документация»- README говорит, в каком выпуске с версии 1.0.0 выходит каждое изменение:
исправление в патч-выпуске, добавление в минорном, ломающее в мажорном.
Протокол движка из
engine.dartв это обещание не входит и может измениться в минорном выпуске; Своё поверх ядра велит движку держатьasync_jobв пределах одной минорной версии. - README называет плагин анализатора
solo_lintsв разделе «Установка». - Описание пакета и первый абзац README говорят, для чего он, словами, которыми его ищут: отмена асинхронной работы и структурная конкурентность.
- Страница о наблюдении говорит, что имя задачи попадает и в сообщения ядра
об ошибках, поэтому в
keyиdescribeкладут идентификатор, а не персональные данные. - Новая страница С riverpod и bloc ставит задачу под провайдер или нотифаер riverpod и обработчик bloc и прерывает вместе с задачей HTTP-запрос http или dio.
- README говорит, что новое значение перечисления или новый подкласс
запечатанного класса выходит в мажорном выпуске и что
switchпоCancelReason, который открыт, держит ветку по умолчанию. - README говорит, что задача не
Future:await jobничего не ждёт, и правилоawait_only_futuresизpackage:lintsэто отмечает. - Страница о стримах говорит, что делает второй
eachпо стриму с одной подпиской: падает задача, которую он создаёт, а сам вызов не бросает. - Страница о времени говорит, что отрицательная пауза считается нулевой.
- Страница правил ошибок называет строку
Job.debugоб отложенной задаче, которую ждут до её запуска.
Ломающие изменения
Заголовок раздела «Ломающие изменения»- Нижняя граница SDK — Dart 3.10.0, прежде была 3.6.0.
Ломающие изменения
Заголовок раздела «Ломающие изменения»- У
JobContextпоявилсяchild, иJobContextBaseего реализует. Класс, который сам реализуетJobContext, его добавляет; подклассJobContextBaseс членом этого имени переименовывает его или делает переопределением; расширение наJobContextс членомchildзатеняется без предупреждения. Движок, который делает своих детей, вcreateEachJobили где-то ещё, или держит правила вcheck(), переопределяетchildс@useResult: по умолчанию этоJob.deferred, и такой движок его отвергнет, или ребёнок не спросит его правил.
Добавлено
Заголовок раздела «Добавлено»ctx.child(body, key:, describe:, timeout:)делает незапущенного ребёнка, которого примет движок задачи, для кода, у которого есть контекст, но который не знает движка. ВJobContextBaseэтоJob.deferred. Движок, чьи дети — его собственные задачи, его переопределяет, иctx.eachберёт ребёнка оттуда же.ctx.childничего не запускает и не проверяет: отказываютctx.run,ctx.startиctx.runAll. См. раздел «Запуск ребёнка»; шаг из раздела «Срок для одного шага» сделан именно так.
Документация
Заголовок раздела «Документация»- Раздел «Тест двигает время» говорит, как тестировать задачу с настоящим вводом-выводом: подделывать только таймеры.
- Раздел «Работа в изоляте» показывает, как выполнить работу в изоляте, который отмена задачи убивает посреди шага.
Ломающие изменения
Заголовок раздела «Ломающие изменения»JobObserver.onErrorпринимает{required JobErrorAnswer answer}: что задача делает с ошибкой.answered: просит за неё ответа, черезJobAnswerer.onUnansweredили зону.cancellation: не попросит никогда, потому что ошибка — отмена.never: не попросит никогда по другой причине, например из-заignoreFailure().later: исход, который несёт ошибку, ещё могут прочитать, и ничего не обещано. Об ошибке, которой не несёт ни один исход, и о провале тела задачи, принявшей отмену к тому моменту, когда тело бросило,Job.isCancellationErrorтеперь спрашивают доonError, а не после:onError, отменивший свою задачу, больше не отправляет эту ошибку под фильтр, о провале, которыйonErrorуже услышал, фильтр, поставленный после этого, не спрашивают, а ошибка бросившего фильтра доходит до зоны раньше, чемonErrorуслышит ошибку, о которой спрашивали фильтр. Ядро больше не оповещает через@protectednotifyObserver: его переопределение слышит только то, что шлёт сам движок, аonErrorслышит каждую такую ошибку сlater, даже послеignoreFailure(). См. «Куда идёт каждая ошибка».
Документация
Заголовок раздела «Документация»- Очередь и правило ушли со страницы «Своё поверх ядра» на свою, «Свой движок».
- Ресурс, переданный другой задаче, уборка без вызова и порядок уборки ушли со страницы «Уборка» на «Правила уборки».
- Путь ошибки каждого вида, отладочный канал, ошибка, в которой несколько провалов, и редкие случаи ушли со страницы «Ошибки» на «Правила ошибок».
Ломающие изменения
Заголовок раздела «Ломающие изменения»Что писать вместо прежнего, слева имя в 0.2.0; у каждого есть своя запись
ниже.
0.2.0 |
0.3.0 |
|---|---|
Job.ignore() |
Job.ignoreFailure() |
JobBase.whenDone |
JobBase.untilFinished |
JobBase.debug |
Job.debug |
JobBase, JobContextBase, JobStatus из package:async_job/async_job.dart |
из package:async_job/engine.dart |
Job<T>, переданный в ctx.run |
ChildJob<T>, или DeferredJob<T> для start() |
ctx.wait |
ctx.abandonable |
JobObserver.onErrorстал уведомлением, а за ошибку, которой больше ни у кого нет, отвечает новыйJobAnswerer: за ту, которой не несёт ни один исход, и заFailed, который никто не наблюдает. Наблюдатель из0.2.0собирается без изменений, а ошибки, которые он раньше глотал, доходят до зоны; см. «Ответ за ошибки».Job.ignore()переименован вJob.ignoreFailure(), аJobBase.whenDone— вJobBase.untilFinished.- Задача продолжается один раз: одним
thenили ручками новогоJob.shared. Второйthen,thenна задаче с ручкой иsharedна задаче сthenбросаютStateError: дайте каждой ветке ручкуshared(), см. «Ручка на каждую ветку». ctx.runпринимаетChildJob. Хэндл, объявленныйJob<T>, там больше не компилируется: объявите егоChildJob<T>, а там, где код вызываетstart(), —DeferredJob<T>, и дайте задачам движкаimplements ChildJob<T>.- Класс, который сам реализует
JobилиJobContext, добавляет новые члены, а подклассJobBaseпереименовывает свой член, чьё имя теперь занято. УJobпоявилисьwhenFinishedиshared, уJobBase—untilFinished, уJobContext—abandonable,heldCancellation,pause,runAll,startи параметрыdisposeиdiscardуrun;then,sharedиeachпринимаютkeyиdescribe, и в переопределении тоже; расширение наJobилиJobContextс именем нового члена затеняется без предупреждения. JobBase,JobContextBaseиJobStatusпереехали вpackage:async_job/engine.dart, аJobBase.debugтеперь называетсяJob.debug. См. «Своё поверх ядра».- Защищённый API движка изменился.
JobContextBase.check,JobBase.finishиJobContextBase.startChildпомечены@mustCallSuper;started,finished,adoptedBy,createContext,execute,createEachJobиbeforeChildStartпомечены@visibleForOverriding;cascadeToChildrenи сеттерlevelубраны, вместо нихcancelWith,cancelOwnJobиstartChild;inUncancellableSection,heldCancel,creationZoneиrequireSharedновые;createEachJobпринимаетkeyиdescribeотctx.each, и переопределение передаёт их задаче, которую создаёт. - Ядро сообщает о
Failed, который движок передаёт вfinish: движок, который вызывалnotifyObserverпередfinish, убирает этот вызов, иначе наблюдатель услышит ошибку дважды. - Отмена внутри
ParallelWaitErrorснова считается отменой: ребёнок, отменённый под[ctx.run(a), ctx.run(b)].wait, заканчивает родителяCancelledтам, где раньше заканчивал егоFailed. - Тело, которое отменяет само себя, отменяется так же, как отменённая
задача: детей просят остановиться, затем выполняются его колбэки
ctx.onCancelи срабатываетwhenCancelled.
Устарело
Заголовок раздела «Устарело»ctx.waitустарел, его заменяетctx.abandonable; старое имя уйдёт в следующем выпуске.
Что вы увидите после обновления
Заголовок раздела «Что вы увидите после обновления»Ошибки, которые 0.2.0 теряла или сообщала только наблюдателю, теперь доходят
до зоны, где создана задача, и тест на них падает:
- провал тела задачи, которая кончилась
Cancelled, например операции, остановленной по токену, который отменила задача; - провал тела задачи, которую движок кончает руками;
- поздняя ошибка действия, которое тело бросило под
ctx.abandonableчерез.timeoutилиFuture.any; - ошибка, которой не несёт ни один исход, у задачи с наблюдателем;
- провал собственной уборки источника, когда
ctx.eachотпускает стрим.
Job.isCancellationError, заданный один раз в main, останавливает ошибки,
которые вызывает отмена, на onError, job.ignoreFailure() глушит первые два
пункта у одной задачи, а за остальные отвечает наблюдатель, который подмешивает
JobAnswerer; см. «Куда уходят ошибки».
Задача, которая сдаётся внутри вызова, которого она не дождалась, больше
не доходит до onError как провал.
ctx.check() в уборке отменённой задачи бросает её Cancelled, а 0.2.0
бросала StateError.
onError слышит каждый провал и каждую отмену внутри ParallelWaitError
отдельным вызовом, а зона получает каждый провал такой ошибки, которую никто
не наблюдает, отдельно; 0.2.0 отдавала всю ошибку один раз.
Отмена ребёнка, которую тело выпустило наружу, печатается как
Cancelled(handler: Job(thumbnail): Cancelled(timeout)), а 0.2.0 печатала
child thumbnail.
JobBase.children отдаёт копию на момент вызова, а 0.2.0 отдавала живое
представление.
С включёнными assert в зону приходит AssertionError, когда движок кончает
двух детей одной задачи одним экземпляром Cancelled.
Задача Job<void> или продолжение then<void>, чьё тело или колбэк возвращает
незапущенную задачу Job.deferred, и ребёнок ctx.each, чей колбэк делает это
на событии, кончаются исходом Failed с ArgumentError; в 0.2.0 они
кончались Done.
Несколько сообщений об ошибках сформулированы иначе: тесту, который сверяет их по тексту, нужны новые формулировки.
Добавлено
Заголовок раздела «Добавлено»ctx.startзапускает ребёнка, которого тело не ждёт; его исход остаётся у того, у кого ребёнок в руках, и не трогает исход родителя.ctx.runAllзапускает детей бок о бок и просит остальных остановиться, как только с одним из них что-то пошло не так, с новой причинойSiblingCancelReason. См. «Когда один провал делает остальное бессмысленным».ctx.pause— задержка, которую кончает отмена.ctx.heldCancellationназывает отмену, которую придерживает секцияuncancellableв момент чтения. См. «Придержать отмену».ctx.runпринимаетdisposeиdiscard, какctx.abandonable.Job.whenFinishedслышит конец задачи, не наблюдая её исход.timeoutдаёт задаче собственный срок, после которого она кончаетсяCancelled(timeout)с новой причинойTimeoutCancelReason.Job.eachсоздаёт корневую задачу, которая следует за стримом.then,sharedиctx.eachпринимаютkeyиdescribeи называют созданную задачу так же, как называется любая; безdescribeеё описание —then,sharedилиeach. См. «Имя задачи».JobObserver.allделает из нескольких наблюдателей одного.JobObserverсталabstract mixin class, чтобы наблюдатель, который наследует другой класс, его подмешивал.Job.isCancellationErrorназывает ошибки, которые вызывает отмена:onErrorих слышит, а отвечать за них не просят никого. См. «Ошибки отмены».Job.visitErrorsотдаёт каждый провал и каждую отмену внутри ошибки в свой колбэк.startedуCancelled.byнеобязателен и по умолчанию равенtrue.- Отладочный канал называет задачу, которая отбросила условные регистрации
отданного ею значения, и задачу, которая кончилась
Done, не отдав такое значение: если оно не лежит внутри результата, его никто не уберёт.
Исправлено
Заголовок раздела «Исправлено»ctx.runиctx.eachотказывают классу, который реализуетJobBase, а не наследует его, с обещаннымArgumentError, а не сNoSuchMethodError.- Значение, которое приходит в вызов
ctx.abandonable, уже прерванный отменой, освобождается сразу; раньше оно удерживалось, пока не кончатся дети, и ребёнок, который ждал ту же блокировку, ждал её вечно. - Ошибка, брошенная
JobBase.started, идёт вonError, а тело выполняется; раньше задача так и шла без тела, вечно. - Задачи, равные по
==, больше не принимаются одна за другую: родитель двух таких детей завершался, пока один из них ещё шёл. - Ключ или
describe, который бросает, печатается в имени задачи меткой, а не подменяет сообщение ядра об ошибке. - Ребёнок, колбэк отмены и слушатель уходят из своего списка без поиска: тысячи таких в одной задаче отнимали время, которое росло как квадрат их числа.
- Тело, которое бросает
Cancelled, принимает эту отмену сразу: пока дети не кончились,isCancelledотвечалfalse, аcancel()ставил свою причину поверх той, которую выбрало тело. - Завершённая задача отпускает своё тело, своего родителя и уборки, которые так
и не выполнила, а задача, созданная внутри работы, отданной
ctx.unattended, отпускает задачу, которая эту работу запустила: сохранённый хэндл держал всё, что они захватили. - Функция снятия, которую возвращает
whenCancelledилиctx.onCancel, отпускает колбэк, как только её вызвали или задача кончилась: владелец, который хранил функции снятия до своего конца, держал всё, что захватили колбэки. - Функция снятия, которую возвращает
ctx.onDisposeилиctx.onDiscard, отпускает уборку и задачу, как только её вызвали, как только уборка отработала и как только задача кончилась, так её и не запустив: владелец, который хранил функции снятия до своего конца, держал задачу и всё, что захватила уборка. - Задача, созданная через
Job(...)внутри работы, отданнойctx.unattended, идёт в зоне, откуда эта работа была запущена, и ошибка гологоunawaitedв ней больше не доходит до наблюдателя задачи, которая запустила работу. ctx.joinосвобождает значение черезdisposeилиdiscard, когда контрольная точка после действия бросает что угодно, а не одинCancelled.ctx.joinиctx.abandonable, вызванные из работы, отданнойctx.unattended, больше не отдают этой работе ресурс, который они уже закрыли.- Значение, которое возвращается после конца тела, больше не заставляет ядро
бросить
Future already completed. - Значение, пришедшее для задачи, которую движок закончил руками, освобождается, а не утекает.
- Дерево задач, у которого на постройке кончился стек, кончается
с
StackOverflowError, а раньше его корень мог остаться работать навсегда; отмена, у которой в глубоком дереве кончается стек, всё равно выполняет колбэкиonCancelтех задач, до которых дошла. - Вызов
ctx.abandonable, которого тело не ждало черезawait, больше не отправляет отмену задачи в зону, когда задача принимает её после конца тела. - Тысячи задач, которые просыпаются вместе, больше не стоят примерно по две миллисекунды каждая под JIT.
- Продолжение
then, приставленное к задаче, за чей провал уже ответили, больше не отвечает за него второй раз.
Документация
Заголовок раздела «Документация»- README стал стартовой страницей, а справочная часть переехала на двенадцать страниц: «Исходы», «Отмена», «Время», «Дети», «Группы», «Цепочки», «Стримы», «Уборка», «Наблюдение», «Ошибки», «Тестирование» и «Своё поверх ядра».
on Cancelled { rethrow; }передcatch, который советовал README0.2.0, записывает операцию, остановленную по своему токену, как провал; раздел «Спросить задачу» вместо этого зовётctx.check()вcatch.
-
Ломающее:
ctx.run(child)возвращаетFuture<T>вместо хэндла ребёнка. Пишитеawait ctx.run(child), чтобы дождаться его значения, детей и уборки; при успешном завершении отмена родителя проверяется до того, как идти дальше. Сохраняйте исходныйchild, чтобы отменять его или смотреть исход. Обрабатывайте ошибки возвращённой future или пишите.ignore()для намеренного параллельного старта, результат которого не нужен. Проверка при старте по-прежнему бросает синхронно.ctx.eachпо-прежнему сразу возвращает хэндл своего ребёнка. -
Добавлен защищённый
JobContextBase.startChildдля приёма ребёнка движком предметной области и проверок старта, общих дляrunиeach, без наблюдения результата ребёнка. -
Добавлен
Job.thenс отдельным контекстом на каждое продолжение, передачей исхода вперёд и отменой в обе стороны. Отмена хвоста ждёт незавершённых предшественников и их уборку.ChainCancelReasonсохраняет каждую соседнюю отмену вcause. Продолжения считаются корневыми задачами ядра с собственным необязательным наблюдателем и не зависят от очередей предметной области. -
Ломающее:
ctx.each(stream, (child, event) { ... })сразу возвращает дочернююJob<void>. Ждите.value, если нужен результат, который бросает, смотрите.doneради исхода или зовите.cancel(), чтобы остановить её отдельно. Колбэк получает контекст ребёнка. Родитель ждёт ребёнка, даже когда его тело его не ждёт, и наблюдатели видят ребёнка. -
Отмена
eachсразу снимает подписку и останавливает доставку событий, а затем ждёт идущий колбэк, прежде чем ребёнок завершится и уберёт за собой. Чтобы отвечать на отмену, пользуйтесь контрольными точками контекста ребёнка; обычныйawaitпрервать нельзя. Уборку источника при отмене подписки по-прежнему не ждут. -
Ломающее:
eachстал методомJobContext; расширениеJobStreamубрано.JobContextBase.createEachJobслужит защищённой фабрикой для движков предметной области. Как иrun,eachотклоняется изunattendedи после конца тела родителя. -
Ломающее: причины стали расширяемыми классами:
ManualCancelReason,ParentCancelReasonиHandlerCancelReason. Замените именованные константы конструкторами и проверяйте типы, а не сравнивайте имена.CancelReasonабстрактный; подклассы могут нести любые данные. -
job.cancel(reason: reason)принимает свою причину, которая сохраняется по идентичности в колбэках и исходах. Тело, бросающееCancelled.by, теперь тоже сохраняет свою явную причину. -
Отмена родителя и отмена ребёнка, вышедшая из тела, сохраняют исходный
CancelledвParentCancelReason.causeиHandlerCancelReason.causeсоответственно. -
Ломающее: future
Job.whenCancelledзаменена наjob.whenCancelled((cancelled) { ... }). Колбэк получает полныйCancelledи выполняется синхронно; регистрация после отмены вызывает его сразу. Возвращённая функция снимает колбэк. -
Неиспользованные слушатели отмены освобождаются, когда задача кончается без отмены. Регистрация не наблюдает исход с провалом.
-
Синхронные ошибки слушателей идут через
onErrorили, без наблюдателя, в зону создания, как уctx.onCancel. Асинхронные колбэки не ожидаются.
Первый выпуск. Ядро задач, вынутое из solo до его собственного первого
выпуска.
Job<T>: отменяемая асинхронная работа с исходом, детьми и кооперативной отменой. Создаётся черезJob(body), которая стартует на следующей микротаске, или черезJob.deferred(body), которая ждётstart().Outcome<T>:Done,Failed,Cancelledс открытымCancelReasonи публичнымCancelled.byдля движков, построенных на этом ядре.- Семейство ожиданий, которое говорит, что отмена делает с вызовом:
ctx.waitкончает ожидание, а не работу,ctx.joinждёт действие целиком и сдаётся после него,ctx.uncancellableудерживает отмену, пока шаг не кончится,ctx.unattendedне ждёт вовсе и отдаёт работу движку,ctx.onCancelпередаёт отмену тому, что умеет остановиться по-настоящему, аctx.checkсдаётся там, где оборачивать нечего.ctx.eachследует за стримом, пока живёт задача, ждёт асинхронныйonDataи забирает подписку с собой при любом исходе. - Стек уборки:
ctx.onDisposeосвобождает при любом исходе,ctx.onDiscardтолько когда задача кончается неDone, аwaitиjoinпринимают те же два какdisposeиdiscardдля значения, которое они передают. Движок разматывает стек после детей и до исхода, дожидаясь каждой уборки;ctx.disown(value)забирает регистрацию обратно, когда тело передаёт значение само. - Дети через
ctx.run: родитель кончается после них, отмена идёт каскадом, а ребёнок, который не отменяется, от неё отказывается. JobObserverсonStart,onFinish,onErrorиonLog; ребёнок наследует наблюдателя родителя. ИсходFailed, который никто не наблюдал, уходит в зону, создавшую задачу, и туда же уходит ошибка, которой больше некуда идти, когда наблюдателя нет.Cancelledтуда не уходит никогда: отмену кто-то решил, это не провал, и услышать её можно только в наблюдателе.ctx.unattended(action)для работы, которую тело запускает и не ждёт: она идёт в собственной зоне ошибок, и всё, что она оставит неперехваченным, сейчас или много позже конца задачи, доходит доonError, а не до процесса.ctx.runиctx.uncancellableизнутри неё отклоняются, а задача, созданная там, сообщает в зону, где идёт тело, а не наблюдателю задачи, которая запустила работу.JobBase<T>иJobContextBaseдля наследования движком предметной области: защищённый API, виртуальная контрольная точкаcheck()и хукиstarted(),finished()иadoptedBy(). В защищённом API есть ещёreportToZoneдля предметной области, у которой собственный путь ошибки, которой некуда идти, ни к кому не приводит (он не пускаетCancelledв зону точно так же, как ядро, и предметной области не приходится писать это правило заново), иthrowIfUnattendedдля члена её контекста, который нельзя вызывать из работы, отданнойunattended.