Запуск нескольких асинхронных фьючерсов в ответ на события

Я хотел бы запустить довольно дорогостоящую операцию в ответ на щелчок пользователя по элементу холста.

mouseDown(MouseEvent e) {
  print("entering event handler");
  var future = new Future<int>(expensiveFunction);
  future.then((int value) => redrawCanvas(value);
  print("done event handler");
}

expensiveFunction() {
  for(int i = 0; i < 1000000000; i++){
    //do something insane here
  }
}

redrawCanvas(int value) {
  //do stuff here
  print("redrawing canvas");
}

Насколько я понимаю, M4 Dart состоит в том, что этот будущий конструктор должен запускать «RoadFunction» асинхронно, иначе говоря, в другом потоке, отличном от основного. И это действительно выглядит так, поскольку «обработчик событий done» сразу же печатается в моем окне вывода в среде IDE, а через некоторое время печатается «перерисовка холста». Однако, если я снова щелкну элемент, ничего не произойдет до тех пор, пока моя «дорогая функция» не будет запущена с предыдущего щелчка.

Как использовать фьючерсы, чтобы просто запустить функцию с интенсивными вычислениями в новом потоке, чтобы несколько из них можно было поставить в очередь в ответ на несколько щелчков мыши, даже если первое будущее еще не завершено?

Спасибо.


person jgon    schedule 20.04.2013    source источник


Ответы (2)


Как упоминалось в другом ответе, фьючерсы - это просто «заполнитель для значения, которое станет доступным в будущем». Они не обязательно подразумевают параллелизм.

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

dart2js может компилировать изоляты в веб-воркеров. Веб-воркер может работать в отдельном потоке.

Попробуйте что-то вроде этого:

import 'dart:isolate';

expensiveOperation(SendPort replyTo) {
  var result = doExpensiveThing(msg);
  replyTo.send(result);
}

main() async {
  var receive = new ReceivePort();
  var isolate = await Isolate.spawn(expensiveOperation, receive.sendPort);
  var result = await receive.first;
  print(result);
}

(Я не тестировал вышеупомянутое, но что-то вроде этого должно работать.)

person Seth Ladd    schedule 20.04.2013
comment
Обратите внимание, что spawnFunction был заменен на Isolate.spawn: stackoverflow.com/a/19870993/339671 - person Kiwi; 26.06.2015

Цикл событий и очередь событий

Обратите внимание, что фьючерсы - это не потоки. Они не работают одновременно, и на самом деле Dart является однопоточным. Весь код Dart выполняется в цикле событий.

Цикл событий - это цикл, который выполняется до тех пор, пока активен текущий изолятор Dart. Когда вы вызываете main() для запуска приложения Dart, изолятор создается, и он больше не активен после завершения основного метода и всех элементов в очереди событий.

Очередь событий - это набор всех функций, выполнение которых еще нужно завершить. Поскольку Dart является однопоточным, все эти функции необходимо запускать по очереди. Поэтому, когда один элемент в очереди событий завершается, начинается другой. Точное время и расписание очереди событий - это нечто более сложное, чем я могу объяснить сам.

Следовательно, асинхронная обработка важна для предотвращения блокировки одного потока каким-либо длительным выполнением. В пользовательском интерфейсе длительный процесс может вызвать визуальное раздражение и затруднить работу вашего приложения.

Фьючерсы

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

Обратный вызов, связанный с этим Future (в вашем случае expensiveFunction), запускается путем добавления в очередь событий. Когда вы возвращаетесь из текущего изолята, выполняется обратный вызов и, как только это возможно, код после then.

Потоки

Поскольку ваши Futures по определению асинхронны, и вы не знаете, когда они вернутся, вы хотите поставить свои обратные вызовы в очередь, чтобы они оставались в порядке.

Stream - это объект, который генерирует события, на которые можно подписаться. Когда вы пишете canvasElement.onClick.listen(...), вы запрашиваете onClick поток MouseEvents, на который вы затем подписываетесь с listen.

Вы можете использовать Streams, чтобы поставить события в очередь и зарегистрировать обратный вызов для этих событий, чтобы запустить нужный вам код.

Что написать

main() {
  // Used to add events to a stream.
  var controller = new StreamController<Future>();

  // Pause when we get an event so that we take one value at a time.
  var subscription = controller.stream.listen(
      (_) => subscription.pause());

  var canvas = new CanvasElement();
  canvas.onClick.listen((MouseEvent e) {
    print("entering event handler");
    var future = new Future<int>(expensiveFunction);

    // Resume subscription after our callback is called.
    controller.add(future.then(redrawCanvas).then(subscription.resume()));
    print("done event handler");
  });
}

expensiveFunction() {
  for(int i = 0; i < 1000000000; i++){
    //do something insane here
  }
}

redrawCanvas(int value) {
  //do stuff here
  print("redrawing canvas");
}

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

Больше информации

См. Также этот отличный ответ на аналогичный вопрос.

Отличное место, чтобы начать читать об асинхронности Dart, является первая часть этой статьи о библиотеке dart: io и эту статью о библиотеке dart: async .

Для получения дополнительной информации о фьючерсах см. эту статью о фьючерсах.

Для получения информации о потоках см. эту статью о добавлении в потоки и эту статью о создании потоков.

person Juniper Belmont    schedule 20.04.2013