Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

async programming in dart

I am relating to java as to how to do thread/async. I use new Thread(target).start() where target is Runnable as one way to do threading in java. New concurrent api has alternatives but we know that on specific call new threads are creating and passed in tasks are executed.

Similarly how is async done in Dart ? I read on send/receivport, completer/future, spawnFunction. To me only spawnFunction is convincing statement that will create new thread. can one explain how completer/future help. i know they take callbacks but is there some implicit logic/rule in javascript/dart that callbacks always be execute in different thread.

below is dart snippet/pseudo code:

void callback() {
  print("callback called");
}

costlyQuery(sql, void f()) {
  executeSql(sql);
  f();
}

costlyQuery("select * from dual", callback);

I hope my costlyQuery signature to take function as 2nd parameter is correct. so now I do not think that f() after executeSql(sql) is going to be async. may be taking above example add completer/future if that can make async to help me understand.

like image 238
Miten Avatar asked Jan 15 '23 23:01

Miten


1 Answers

tl;dr: There is no implicit rule that a callback will not block.

Javascript event queue

In Javascript, there are no threads (except WebWorkers, but that's different). This means that if any part of your code blocks, the whole application is blocked. There is nothing magic about callbacks, they are just functions:

function longLoop(cb) {
    var i = 1000000;
    while (i--) ;
    cb();
}

function callback() {
    console.log("Hello world");
}

function fun() {
    longLoop(callback);
    console.log("Called after Hello World is printed");
}

To make something asynchronous, the callback must be put on the event queue, which only happens through some API calls:

  • user initiated event handlers- clicks, keyboard, mouse
  • API event handlers- XmlHTTPRequest callbacks, WebWorker communication
  • timing functions- setTimeout, setInterval

When the event is triggered, the callback is placed on the event queue to be executed when all other callbacks have finished executing. This means that no two lines of your code will ever be executing at the same time.

Futures

I assume you have at least stumbled on this post about futures. If not, it's a good read and comes straight from the horses mouth.

In Javascript, you have to do some work to make sense of asynchronous code. There's even a Javascript library called futures for doing common things, such as asynchronous loops and sequences (full disclosure, I have personally worked with the author of this library).

The notion of a future is a promise made by the function creating the future that the future will be completed at some point down the road. If you are going to make some asynchronous call, create a future, return it, and fulfill the promise when the asynchronous call finishes. From the blog post:

Future<Results> costlyQuery() {
    var completer = new Completer();

    database.query("SELECT * FROM giant_table", (results) {
        // when complete
        completer.complete(results);
    });

    // this returns essentially immediately,
    // before query is finished
    return completer.future; 
}

If database.query is a blocking call, the future will be completed immediately. This example assumes that database.query is a non-blocking call (async), so the future will be fulfilled after this function exits. completer.complete will call whatever function is passed to completer.then() with the arguments specified.

Your example, modified to be idiomatic and asynchronous:

void callback() {
  print("callback called");
}

costlyQuery(sql) {
  var completer = new Completer();
  executeSql(sql, () => completer.complete());
  return completer.future;
}

costlyQuery("select * from dual").then(callback);

This is asynchronous and uses futures correctly. You could pass your own callback function into costlyQuery as you have done and call that in the callback to executeSql to achieve the same thing, but that is the Javascript way of doing it, not the Dart way.

Isolates

Isolates are similar to Java's threads, except that isolates are a concurrency model and threads are a parallelism model (see this SO question for more information about concurrency vs parallelism).

Dart is special in that the specification does not mandate that everything be run in a single thread. It only mandates that different executing contexts do not have access to the same data. Each isolate has it's own memory and can only communicate (e.g. send data) with other isolates through send/receive ports.

These ports work similarly to channels in Go if you're familiar.

An isolate is an isolated execution context that runs independently of other code. In Javascript terms, this means that it has it's own event queue. See the Dart tour for a more complete introduction to isolates.

Lets say your executeSql is a blocking function. If we don't want to wait for it to finish, we could load it into an isolate:

void callback() {
    print("callback called");
}

void costlyQuery() {
    port.receive((sql, reply) {
        executeSql(sql);
        reply.send();
    });
}

main() {
    var sendPort = spawnFunction(costlyQuery);

    // .call does all the magic needed to communicate both directions
    sendPort.call("select * from dual").then(callback);

    print("Called before executeSql finishes");
}

This code creates an isolate, sends data to it then registers a callback for when it's done. Even if executeSql blocks, main() will not necessarily block.

like image 170
beatgammit Avatar answered Jan 20 '23 15:01

beatgammit