Skip to content

Latest commit

 

History

History
265 lines (180 loc) · 11.7 KB

README.ru.md

File metadata and controls

265 lines (180 loc) · 11.7 KB

pub package

Языки:

English Russian

Введение

Ранее в целях упрощения взаимодействиями с изолятами в Dart я начал разрабатывать акторный фреймворк - Theater.

Однако не всем может прийти по вкусу как сама акторная модель которую реализуют изоляты в Dart так и акторный фреймворк.

Помимо Dart-а я так же пишу на C# и там есть, как по мне, удобная обертка для работы с многопоточностью - Task.

Я решил сделать в Dart нечто максимально похожее на Task в C#, однако с некоторыми ньюансами из за того что все же под капотом используются изоляты.

Про Ossa

Предоставляет удобную обертку для работы с изолятами похожую на Task в C#.

Установка

Добавьте Ossa в ваш pubspec.yaml файл:

dependencies:
  ossa: ^1.0.4

Импортируйте ossa в файле где он должен использоваться:

import 'package:ossa/ossa.dart';

Что такое задача

Каждая задача запускается и работает в отдельном изоляте, задачи могут возвращать некий результат.

Как уже было сказанно в введении я пытался сделать задачи максимально похожими на Task в C#. Однако есть и различия.

К примеру отсутствие вместо пула изолятов (в C# пул потоков) к которым переадресуются задачи и затем выполняются, в Ossa каждая создаваемая вами задача имеет собственный изолят.

Каждая задача управляет жизненным циклом своего изолята.

Типы задач

Задачи бывают двух типов:

  • одноразовые задачи (по умолчанию);
  • переиспользуемые задачи.

Различие между этими двумя типами состоит в том что одноразовые задачи после завершения выполнения задачи (в том числе если выполнение завершилось ошибкой) - вызывают свой метод dispose, уничтожают свой изолят и закрывают все используемые ими StreamController-ы, то есть освобождает все используемые ей ресурсы. Переиспользуемые задачи этого не делают, расчитанно это на то что вы создав задачу будете использовать её неоднократно.

Зачем использовать переиспользуемую задачу, а не создавать в будущем, при необходимости новую задачу? - чтобы каждый раз не создавать новый изолят.

Запуск задачи

Запустить задачу можно двумя способами:

  • при помощи метода run;
  • создав задачу, проиницализировав её и самостоятельно её запустив.

Пример запуска при помощи метода run:

void main() async {
  // Create and run Task using run method
  var task = await Task.run((context) {
    print('Hello, from task!');
  });

  // Wait when task is completed
  await task.result();
}

Пример запуска создавая задачу, проинициализировав её и запустив:

void main() async {
  // Create task
  var task = Task((context) {
    print('Hello, from task!');
  });

  // Initialize task before work with him
  await task.initialize();

  // Start task
  await task.start();

  // Wait when task is completed
  await task.result();
}

Получение результата

Задача может возвратить результат выполнения. При создании задачи самостоятельно, либо создании и запуске при помощи метода run вы можете указать Generic тип, который должен вернуть Task.

Можно обработать получение результата как ассинхронно при помощи onDone обработчика, так и дождаться получения результата при помощи Future полученного при вызове метода result.

Метод result в случае когда задача выполняется подождет результата из изолята, а затем вернет его. Если же задача на момент вызова метода result уже завершена и не запущенна повторно, то он вернет результат от предыдущего запуска задачи.

Получение результата из Future полученного при вызове метода result:

void main() async {
  // Create and run Task with double return type using run method
  var task = await Task.run<double>((context) => 3 * 7);

  // Wait result from Task
  var result = await task.result();

  print(result);
}

Ожидаемый вывод:

21

Ассинхронное получение результата при помощи onDone обработчика:

void main() async {
  // Create and run task with int return type, set onDone handler
  var task = await Task.run<int>((context) {
    return 5 * 10;
  }, onDone: (value) async {
    print(value);
  });
}

Ожидаемый вывод:

50

Передача данных в задачу

Каждая задача выполняется в своем собственном изоляте. Изоляты не имеют разделяемой друг с другом памяти. Поэтому те данные с которыми должна работать задача предаются в неё при помощи параметра data в run и start методов.

Пример передачи данных в задачу:

void main() async {
  // We have some data
  var number = 100;

  // Create and run task using run method, passing data to task
  var task = await Task.run<int>((context) {
    var number = context.get<int>('number');

    return number * 10;
  }, data: {'number': number});

  // Wait when task is completed
  var result = await task.result();

  print(result);
}

Ожидаемый вывод:

1000

Статус задачи

Задача в ходе своего жизненного цикла имеет различные статусы:

  • не инициализированна (notInitialized);
  • ожидает запуска (waitingToRun);
  • выполняется (running);
  • приостановлена (paused);
  • очищена (disposed).

Не инициализированна задача имеет при создании без помощи метода run, до выполнения метода initialize. В этот момент изолят задачи еще не создан и требует инициализации прежде чем задача сможет выполнится.

После запуска метода initialize задача находится в ожидании запуска. Она готова выполнится и ждет выполнения метода start.

В статусе выполнения задача находится после выполнения метода start, может быть приостановлена (приостанавливается изолят задачи).

Если задача приостановлена, то её выполнение может быть возобновлено.

После очистки задачи её изолят уничтожается, все StreamController-ы закрываются. Дальнейшее использование этого экземпляра задачи невозможно.

Обработка ошибок

Во время выполнения задачи может возникнуть исключение.

Есть варианта его обработки:

  • обработка при помощи onError обработчика;
  • заключение части кода с ожиданием результата в try/catch.

Пример обработки исключения при помощи onError обработчика, ассинхронно обрабатывая результат выполнения задачи:

void main() async {
  late Task task;

  // Create and run Task using run method, set onError handler
  task = await Task.run<void>((context) {
    throw FormatException();
  }, onError: (error) async {
    print(error.object.toString());

    task.dispose();
  });
}

Если при старте задачи не был задан onError обработчик, то есть 2 сценария что произойдет с ним:

  • если вы ожидаете результат задачи ассинхронно, то есть при помощи onDone обработчика, то ничего не произойдет, исключение не будет никак обработанно. Задача из статуса выполнения (running) перейдет в статус готова к выполнению (waitingToRun);
  • если вы ожидаете результат задачи при помощи метода result, то исключение будет вызванно повторно вызванно уже в методе result.

Пример обработки исключения без onError обработчика, ожидая результат при помощи метода result:

void main() async {
  // Create and run Task using run method
  var task = await Task.run((context) {
    throw FormatException();
  });

  try {
    // Wait when task is completed
    await task.result();
  } catch (object) {
    // Handle error

    if (object is TaskCompleteException) {
      print(object);

      await task.dispose();
    }
  }
}