Отмена
cancel() запрашивает; тело отвечает в контрольной точке. Какая это
точка, решает, что станет с операцией за ней:
Job<void>((ctx) async { // Ожидание кончается сразу; запрос идёт дальше, а его значение // отбрасывается или уходит в колбэк освобождения. final rows = await ctx.wait(db.readAll);
// Этого дожидаются до конца, и только потом вместо значения выходит // отмена. await ctx.join(() => db.migrate(stop));
// Пока это идёт, Job не помечается; бросит следующая точка. await ctx.uncancellable(() => db.markReady(stop));
// Между шагами вычисления оборачивать нечего. ctx.check(); use(rows);});| Метод | Если отмена приходит во время ожидания |
|---|---|
ctx.wait(action) |
Сразу бросает Cancelled. Действие продолжается; его результат отбрасывается или уходит в колбэк освобождения. |
ctx.join(action) |
Ждёт действие, затем бросает вместо возврата значения. Колбэк освобождения для этого значения дожидается первым. |
ctx.uncancellable(action) |
Удерживает запрос до конца секции: ни onCancel, ни передачи отмены детям, пока она идёт. |
ctx.check() |
Бросает, если Job уже принял отмену. |
После принятия запроса методы check, wait, join, uncancellable,
run и each бросают Cancelled на своих контрольных точках.
onCancel тоже бросает исключение при запоздалой регистрации:
уведомление об отмене уже произошло. onDispose, onDiscard, disown
и unattended остаются доступны, чтобы тело могло организовать уборку
после отмены.
Если действие, ожидаемое через join, завершится ошибкой, она будет
передана без изменений даже после отмены, а принятая отмена самого Job
останется в силе. Используйте join, если работа должна закончиться или
остановиться до уборки, например для команды, уже отправленной
устройству. С wait поздний результат отбрасывается или передаётся
заданному колбэку освобождения: если задача ещё завершается, колбэк
добавляется в её стек уборки и выполняется до завершения; если задача
уже завершилась, колбэк выполняется отдельно.
Всегда ожидайте ctx.uncancellable. Секция открывается при вызове,
даже если её future не ждут. Без await секция может продолжить работу
после окончания тела; если Job завершится раньше, отложенная отмена
потеряется и cancel() может вернуться с исходом Done. Если нужно
защитить всё тело, создайте Job(body, cancellable: false). Он отклоняет
обычную отмену после запуска тела, но до запуска его всё ещё можно
отменить. Библиотека, построенная на ядре, может принудительно отменять
задачи по своим правилам.
Внутри действия, переданного контексту, обычный await уместен.
Несколько шагов внутри ctx.uncancellable могут завершиться вместе:
контекст управляет всем действием и не добавляет контрольных точек между
его внутренними шагами. Если отдельным шагам нужны свои проверки отмены,
добавьте обращения к контексту и там.
Перехват без проглатывания
Заголовок раздела «Перехват без проглатывания»Cancelled реализует Exception. Если вы ловите Exception или Object,
пробрасывайте отмену задачи дальше, чтобы не продолжать работу после неё.
Можно выделить для Cancelled отдельную ветку catch:
try { await ctx.join(() => database.migrate(stop));} on Cancelled { rethrow; // эту — никогда не глотать} on Exception catch (error) { ctx.log('migration failed: $error');}Или проверить тип внутри общего обработчика:
try { await ctx.join(() => database.migrate(stop));} on Object catch (error) { if (error is Cancelled) rethrow; ctx.log('migration failed: $error');}В обоих вариантах общий обработчик может ловить как Exception, так
и Object. Если Job уже принял отмену, перехват его Cancelled не
снимает эту отмену. Код после catch может выполниться, но следующая
контрольная точка контекста снова бросит исключение. При завершении
исходом всё равно будет Cancelled, даже если тело вернёт значение.
По возможности ловите конкретные типы ошибок и пропускайте отмену
самого Job дальше.
Перехваченный Cancelled сам по себе не означает, что этот Job отменён.
Если операция бросила его, а тело перехватило, Job может завершиться
с Done, если сам не принимал отмену. То же относится к отмене из
await child.value: её можно перехватить, если дочерняя задача была
необязательной. Вызовите ctx.check() внутри блока catch, чтобы
проверить родителя: он бросит исключение, если родитель тоже отменён.
Остановка самой операции
Заголовок раздела «Остановка самой операции»Отмена помечает задачу. Чтобы остановить работу за ней, нужен собственный механизм операции:
// Попросить саму операцию остановиться и дождаться, что она это сделает.ctx.onCancel(stop.cancel);await ctx.join(() => database.migrate(stop));
// Когда остановка сама асинхронна.ctx.onCancel(() => ctx.unattended(device.stop));ctx.onCancel(callback) вызывается синхронно при принятии отмены,
до контрольной точки в теле. Используйте его для отмены токена, запроса
или подписки.
Dart позволяет передать async-колбэк прямо в onCancel, но его future
не будет ожидаться — отсюда unattended выше. В onError попадут только
синхронные ошибки колбэка, а асинхронные уйдут в зону.
Работа, которую задача не ждёт
Заголовок раздела «Работа, которую задача не ждёт»// Ни ожидания, ни отмены, а ошибки всё равно принадлежат этой задаче.ctx.unattended(() => analytics.send('migrated'));ctx.unattended(action) оставляет ошибки работы связанными с задачей,
даже после её завершения: они передаются наблюдателю, а без него в зону
создания задачи. unawaited(...) только подавляет предупреждение
анализатора и не обеспечивает такой обработки ошибок. Запускайте работу
внутри колбэка и оставляйте её future и другие асинхронные объекты
внутри него: он выполняется в отдельной зоне ошибок.