async_job
async_job добавляет к асинхронному коду на Dart отмену, дочерние задачи
и освобождение ресурсов. Пакет работает на чистом Dart, из зависимостей
только meta.
Job<T> выполняет функцию, которая называется телом задачи.
Через await job.done можно получить исход работы, а через
await job.value значение, возвращённое телом. Сам Job не реализует
Future.
У обычной Future нет отмены. Флаг может попросить работу остановиться,
но за то, что она уже открыла, не отвечает никто:
var cancelled = false;
Future<void> load() async { final db = await Database.open(); if (cancelled) { return; // база остаётся открытой } final rows = await db.readAll(); if (cancelled) { return; // и здесь тоже } use(rows);}Job отвечает:
final job = Job<void>((ctx) async { // Проверка перед вызовом и ещё раз перед возвратом значения, // а база закрывается, чем бы Job ни кончилась. final db = await ctx.join(Database.open, dispose: (db) => db.close()); use(await ctx.join(db.readAll));});
// Ждёт тело, его детей и его уборку.await job.cancel();print(job.outcome); // Cancelled(manual)Job управляет этим жизненным циклом. Он завершается одним из трёх исходов:
Done, Failed или Cancelled с причиной. Перед завершением он ждёт
дочерние задачи и выполняет зарегистрированную уборку. Если исход Failed
никто не наблюдал, ошибка передаётся в зону создания задачи, как
необработанная ошибка Future в Dart. Для ошибок работы, которую тело
уже не ждёт, Job также предоставляет наблюдателя.
Отмена кооперативная: cancel() запрашивает её, а тело останавливается
на контрольной точке. Job предоставляет эти точки через контекст ctx,
переданный телу. ctx.join(action) проверяет отмену перед запуском
операции, дожидается её завершения и проверяет отмену ещё раз, прежде
чем вернуть значение телу.
Прямой await action() допустим, но отмену Job не проверяет.
Он продолжает ждать, а код после него может выполниться, даже если
задача уже отменена. Поэтому использование контекста входит в правила
написания отменяемого тела. CancelableOperation отменяет ожидание;
Job владеет тем, что работа оставила после себя.
В async_job нет управления состоянием, очереди и правил планирования.
Если они нужны, используйте solo: он добавляет эти возможности поверх
async_job и реэкспортирует его API.
Повторы, таймауты и пул задач не входят ни в один из пакетов; их можно добавить в приложении.
Установка
Заголовок раздела «Установка»dart pub add async_jobБыстрый старт
Заголовок раздела «Быстрый старт»Допустим, нужно открыть базу, выполнить миграцию и вернуть открытую базу вызывающему коду. При ошибке или отмене базу нужно закрыть. Контекст позволяет описать оба варианта в теле:
import 'package:async_job/async_job.dart';
final job = Job<Database>((ctx) async { final database = await ctx.join( Database.open, discard: (database) => database.close(), );
final stop = CancelToken(); ctx.onCancel(stop.cancel);
await ctx.join(() => database.migrate(stop)); await ctx.uncancellable(() => database.markReady(stop));
return database;});
// Кто-то передумал, пока база открывалась.await Future<void>.delayed(const Duration(milliseconds: 10));await job.cancel();
final outcome = await job.done; // Cancelled(manual)- Тело запускается на следующей микротаске. Вызывающий код сначала
получает задачу и может зарегистрировать обработчики до её запуска.
Если отменить задачу сразу после создания, тело не выполнится, а исходом
станет
Cancelled(manual)сstarted: false. Задержка в примере даёт телу запуститься. ctx.join(Database.open)вызываетDatabase.openи возвращает открытую базу. Если во время открытия придёт отмена, метод дождётся завершения вызова и броситCancelled, если открытие прошло успешно. Если открытие завершится ошибкой, он передаст её без изменений, даже после отмены.discard: (database) => database.close()закрывает базу, если задача завершается отменой или ошибкой. ПриDone(database)база остаётся открытой для вызывающего кода. Уборка учитывает и отмену послеreturn database: например, если тело запустило дочернюю задачу, которая ещё работает, перед завершением задача будет её ждать. Отмена во время этого ожидания приведёт к исходуCancelledи закрытию базы. См. Уборку.ctx.onCancel(stop.cancel)связывает отмену задачи с токеном отмены базы. Колбэк вызывается сразу после принятия отмены, до следующей контрольной точки в теле.ctx.join(() => database.migrate(stop))ждёт завершения миграции. При отмене токен просит миграцию остановиться, аjoinждёт её остановки, прежде чем задача закроет базу. Если нужно прекратить ожидание сразу при отмене, используйтеctx.wait. Он прекращает ожидание, но не останавливает саму операцию.ctx.uncancellable(() => database.markReady(stop))защищает этот заключительный шаг от сигнала остановки. При миграцииjoinждёт, пока база останавливается по токену. Здесь шаг должен завершиться, не получая такого сигнала, поэтомуuncancellableоткладывает запрос отмены:onCancelне срабатывает и токен остаётся активным во время вызова. После секции запрос вступает в силу, а следующая контрольная точка контекста бросаетCancelled. См. Отмену.await job.cancel()запрашивает отмену и ждёт завершения задачи. Это ожидание включает уборку, поэтому исход на следующей строке уже готов. Если достаточно только запросить отмену,awaitможно опустить.
Полный запускаемый пример с имитацией Database находится в
example/example.dart.
Страницы
Заголовок раздела «Страницы»Они же на сайте документации, с поиском.
| Страница | О чём |
|---|---|
| Исходы | Done, Failed, Cancelled и кто отвечает за ошибку |
| Отмена | Контрольные точки, onCancel, uncancellable, причины |
| Дети, стримы и цепочки | ctx.run, ctx.each, then |
| Уборка | dispose, discard, onDispose, порядок |
| Наблюдение и тестирование | JobObserver, логи, фейковое время |
| Своё поверх ядра | JobBase, отложенный старт, свой движок |
solo добавляет состояние, очередь
и декларативные правила. Он выполняет одну корневую задачу за раз
и даёт ей исключительный доступ к состоянию. Используйте его, если нужен
контроллер с такими гарантиями. Он реэкспортирует async_job, поэтому
достаточно зависимости от solo.
Задачи работают везде, где работает Dart. Для интеграции solo
с виджетами используйте flutter_solo.