1

I would like to launch a fairly expensive operation in response to a user clicking on a canvas element.

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");
}

My understanding of M4 Dart, is that this future constructor should launch "expensiveFunction" asynchronously, aka on a different thread from the main one. And it does appear this way, as "done event handler" is immediately printed into my output window in the IDE, and then some time later "redrawing canvas" is printed. However, if I click on the element again nothing happens until my "expensiveFunction" is done running from the previous click.

How do I use futures to simply launch an compute intensive function on new thread such that I can have multiple of them queued up in response to multiple clicks, even if the first future is not complete yet?

Thanks.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
jgon
  • 55
  • 4

2 Answers2

3

As mentioned in a different answer, Futures are just a "placeholder for a value that is made available in the future". They don't necessarily imply concurrency.

Dart has a concept of isolates for concurrency. You can spawn an isolate to run some code in a parallel thread or process.

dart2js can compile isolates into Web Workers. A Web Worker can run in a separate thread.

Try something like this:

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);
}

(I haven't tested the above, but something like it should work.)

Seth Ladd
  • 112,095
  • 66
  • 196
  • 279
  • 1
    Note that `spawnFunction` has been replaced by `Isolate.spawn`: http://stackoverflow.com/a/19870993/339671 – Kiwi Jun 26 '15 at 14:15
1

Event Loop & Event Queue

You should note that Futures are not threads. They do not run concurrently, and in fact, Dart is single-threaded. All Dart code runs in an event loop.

The event loop is a loop that runs as long as the current Dart isolate is alive. When you call main() to start a Dart application, the isolate is created, and it is no longer alive after the main method is completed and all items on the event queue are completed as well.

The event queue is the set of all functions that still need to finish executing. Because Dart is single threaded, all of these functions need to run one at a time. So when one item in the event queue is completed, another one begins. The exact timing and scheduling of the event queue is something that's way more complicated than I can explain myself.

Therefore, asynchronous processing is important to prevent the single thread from being blocked by some long running execution. In a UI, a long process can cause visual jankiness and hinder your app.

Futures

Futures represent a value that will be available sometime in the Future, hence the name. When a Future is created, it is returned immediately, and execution continues.

The callback associated with that Future (in your case, expensiveFunction) is "started" by being added to the event queue. When you return from the current isolate, the callback runs and as soon as it can, the code after then.

Streams

Because your Futures are by definition asynchronous, and you don't know when they return, you want to queue up your callbacks so that they remain in order.

A Stream is an object that emits events that can be subscribed to. When you write canvasElement.onClick.listen(...) you are asking for the onClick Stream of MouseEvents, which you then subscribe to with listen.

You can use Streams to queue up events and register a callback on those events to run the code you'd like.

What to Write

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");
}

Here we are queuing up our redrawCanvas callbacks by pausing after each mouse click, and then resuming after redrawCanvas has been called.

More Information

See also this great answer to a similar question.

A great place to start reading about Dart's asynchrony is the first part of this article about the dart:io library and this article about the dart:async library.

For more information about Futures, see this article about Futures.

For Streams information, see this article about adding to Streams and this article about creating Streams.

Community
  • 1
  • 1
Juniper Belmont
  • 3,296
  • 2
  • 18
  • 28