Hacker News new | comments | ask | show | jobs | submit login
Flutter: Futures, Isolates, Event Loop (didierboelens.com)
193 points by yannikyeo 25 days ago | hide | past | web | favorite | 39 comments

This is a great writeup, wish I had it when I started with Flutter. BTW, I've written a couple Flutter apps now and this set of tools (Futures, async/await, Isolates) covers my multithreading use cases in a really nice and safe way.

By comparison, Grand central dispatch is pretty nice on iOS, but I really miss the first class async/await calls. Side note: How did they make an entirely new language for app development (Swift) and not build in async/await support?

Kotlin co-routines seem to be a pretty nice solution on Android, but before that it was a bit of a mess. There was 4 or 5 different ways to do things async, they all interact in super complicated ways with the activity life cycle, and basically nothing is quite what you need.

Side side note: Unity also does a really nice version of all of this with a single event loop and really powerful co-routines. Worth checking out sometime.

>By comparison, Grand central dispatch is pretty nice on iOS, but I really miss the first class async/await calls. Side note: How did they make an entirely new language for app development (Swift) and not build in async/await support?

By building it gradually and not rushing everything from the first day.

That said:


I think this is a question of priorities rather than rushing things. Swift is a post Moore's Law language after all.

Android screwed up by putting AsyncTask in their first set of documentation. Its easy to show demos with but hard to get right in a safe way.

There are some interesting things that Unity coroutines do, like the idea that routines are tied to owners that are running them, so when the owner's life cycle ends, the coroutines no longer continue. However, as a whole I think there's a lot wrong with them. They're not any safer than Android's async functionality, they just make doing anything off the main thread almost impossible so threading issues are easier to deal with. The whole wait and continue functionality of them is also very frustrating in implementation with many exceptions and hacks.

If you want to see the big picture regarding swift and concurrency i recommend reading :


Swift 5 will most likely include async/await

Whenever that is. It was originally slated for the last year.

We used to wait decades for any meaningful change in languages like C, C++, Java, JS, etc...

Sure, but these were languages that arrived with at least one huge improvement over the languages that existed at the time. Dart's async/await coupled with Flutter's proper support of it in their SDK is that huge improvement for me in mobile dev.

In my experience, Swift is lacking any particular major improvement over ObjC. It seems like async/await could have been that big step, and Swift probably shouldn't have been adopted until it was in place. Just wasn't worth it.

I realize that's a controversial topic and I know people who love Swift, just from my perspective it was such an enormous cost without enough upsides to justify it.

I think type safety is Swift’s killer app. Other than that I don’t know what other major improvements over Objective-C other than the general conveniences of pithy modern languages.

I'm not sure how what we used to do or wait for is relevant now. Technology and it's pace isn't the same as it was decades ago, and I see no reason that we should hold it to the standard of that time.

>I'm not sure how what we used to do or wait for is relevant now.

Because whether a language is slow to evolve only makes sense relatively to some baseline rate.

>Technology and it's pace isn't the same as it was decades ago, and I see no reason that we should hold it to the standard of that time.

And yet still, today, popular languages take their time to get features like async/await. Python got it after 3.6 (so 20+ years on), JS only got it in 2017, C++ doesn't have it, Rust doesn't yet have it (it's coming), Java doesn't have it, C++ doesn't have it...

Heck, Golang doesn't have generics and comfortable error checking still...

It really is a language by language basis. Many languages adopt features insanely fast, many insanely slow. JS for example has as of late has a very fast rate of change[1]. Or you could look at how Java has changed from 8->10 and all the new features added there. There are other languages like C that haven't changed much at all (to my knowledge). It isn't relevant to take the rate of change of other languages and apply it to another as an expectation. You would get a better metric by taking the rate of change of the current language in the context of the last year or two.

I'm not sure async await was as much in the limelight at the start of development on swift, but I could be wrong.

It's been part of F# since 2007 and C# since 2013, so the feature was probably known at design time of the language. However, language design is always a complicated dance of trade-offs, so there's never a way to initially account for all features that are eventually desired and implemented.

I've been getting into Flutter and Dart recently after being a long time React fan. I must say I like many things Flutter does differently. Especially around its Material UI package (built-in) which is miles above any other web material component library in any framework (React, Vue, Angular, etc.)

I must say wrapping my head around the addition of 'await' and the changes it made to the EventLoop processing was difficult but as I type this comment I realize I do fully grok it now. I must say that this is IMO probably the most important piece of info in the article.


"`await` causes immediate execution of the Future (up to the first `await` in the Future) verses appending the Future to the end of the EventLoop queue"

If I have the above wrong someone please call me out.

Great article.

Await doesn’t immediately execute the future; see the example code in the article.

It looks like the Dart concurrency model is basically the same as JS except with microtasks and replacing “worker” with “isolate.”

I'm not sure I agree.

In the first example `await` is added before the Future creation and this causes immediate execution in the printed output (second version). In the first version both Futures are pushed onto the EventTask queue and are executed after main() has finished.

In the second example with method1 and method2, in method1 with async (which signals a Future) on the forEach, all three Futures are pushed to the EventLoop queue and execute after method1() has finished. In method2() there is no new Future being added and execution happens immediately with the end of method2 executing after all delayedPrints.

You also see the effect with all three Futures resolving at once in the method1 case due to all three being added to the queue at the same time each with the same delay. Whereas with method2 each print with a delay after due to this function being synchronous with nothing going on the EventLoop.

Await pushes your task onto the end of the event queue; this might seem like you’re immediately executing a future in many cases but it’s not true in all cases. Other futures created by your thread might take priority. Consider the case where you pushed a future onto the event loop which infinite loops before you used await. Then your code will freeze.

Um, microtasks are in the Js/browser model, too: https://jakearchibald.com/2015/tasks-microtasks-queues-and-s...

Didn’t know that, thanks

I'm no functional fanatic, but one thing that I enjoy about it is the idea that function calls- and futures- resolve to a value.

The code example where D doesn't get called last because of the missing await grinds my gears something fierce, especially because I missed it the first glance through. This type of bug is much harder to create when you don't constantly rely on side effects everywhere.

That said, the article did a really good job if you're not already familiar with the same concepts from JS land (with the event loop sans visible microtasks, Promise instead of Future, abd web worker instead of Isolate).

This is my main point of criticism about Dart (and Flutter SDK design). While Flutter is incredibly pleasant to work with compared to native Android or iOS SDKs, it still feels so terribly ill-conceived in so many areas coming from a functional background.

For the Dart part the Future-problems you already mentioned and the need for async/await language level keywords is disappointing. Also if/else not being an expression as well as syntactic complexity (e.g. defining constructors with factory/static/const keyword combinations). Why come up with a new language in 2011 that repeats the null mistake?

Flutter gives you a beautiful and comprehensive set of widgets and makes a great first impression. But when digging a bit deeper, things tend to get ugly. Massive side effects hidden deep inside OOP structures (e.g. routing), stuff like mixins, validation tied into view layer etc. I couldn't help but find so many mistakes being repeated.

After spending a couple months with Flutter, I decided to ditch mobile development all together. I wish they followed the direction react is steadily going: promoting simple, pure widgets and passing values around, avoiding side effects.

> the need for async/await language level keywords is disappointing

This is unfortunate [1], I agree. The challenge is that if you don't do that, it's very hard to compile the resulting code to efficient JavaScript. Dart was initially only a web language and is now both a web and a mobile language.

You can think of function calls as having two different calling conventions: return the value normally, or return it by invoking a continuation with the result. Asynchronous functions require the latter. You could compile all function calls to that style, but it's much slower and generates larger JS.

You could try to infer which functions should be called using an async style and which shouldn't. That works in simple cases, but breaks down in complex cases involving generics and higher-order functions. I don't know of any literature that investigates how feasible this is in practice, so making your language rely on this optimization for tolerable performance is a very large gamble.

This may change over time now that JS has generators and may be adding async/await syntax, but Dart was designed before either of those existed.

> Also if/else not being an expression

Yeah, everything being an expression is nice. I wish Dart had been designed that way. The original designers of the language believed they needed to be very conservative in order to be successful and I think they overshot that mark.

> syntactic complexity (e.g. defining constructors with factory/static/const keyword combinations)

Some of this is annoying, yes. It does have some practical value, though:

* The restriction around initializing final fields in the constructor initialization list, which is what forces you to use factory constructors in some cases, ensures a very nice property: it means in Dart you can never observe a final field before it has been initialized. This is not true in C#, Java, or Kotlin. Now that we are working on non-nullable types for Dart [2], this will let us have sound non-nullable types, unlike those other languages.

* Personally, I find const to be more of a hassle than it's worth, but const constructors do let you define real compile-time constant objects of user-defined classes, which isn't something some other languages can express. That, in turn, lets you rely on very fast identity tests for checking equality between two objects. Flutter relies on this heavily when diffing the widget tree to determine which parts have changed efficiently.

> Why come up with a new language in 2011 that repeats the null mistake?

Tell me about it [3]. (Note the date on that blog post.) We're working to fix it now.

[1]: http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...

[2]: https://github.com/dart-lang/language/issues/110

[3]: http://journal.stuffwithstuff.com/2011/10/29/a-proposal-for-...

If you're interested in the Dart VM, I also recommend taking a look at Vyacheslav Egorov's Introduction to Dart VM (https://mrale.ph/dartvm/).

What is the role of the `async` keyword in Dart? I couldn't understand it from the article.

> the outcome of the method is a Future

Isn't the function signature sufficient to express this?

> it runs synchronously the code of that method up to the very first await keyword

How is this different from non-async methods?

> the next line of code will be run as soon as the Future, referenced by the await keyword, will have completed

But the await keyword is in the caller which is not statically known.

What is the difference between an async method and a method that returns a Future?

It works like in every other language that supports async/await. Compiler will convert code inside async method into a state machine to prevent it from locking thread during await calls.

If you don't uses async you have to do compiler's job yourself.

But why do we need a new keyword to express this? Isn't the return type sufficient?

No, because you can write async functions without using async/await. Using callback for example:

  Future<int> foo() {
      return Future.delayed(Duration(seconds: 1)).then((x) => 1);
In this case there is no compiler magic needed. If you ask why async keyword is needed if we can just infer it from await inside the method body - probably only for readability.

async is part of the method signature, the public facing part of the method. But it controls an implementation detail, namely whether the function body may use await (as you say asynchronous functions are not required to use await).

I found a link [1] that described async as a compatibility affordance in C# 5: legacy code may use await as a variable name, so async opts into the contextual await keyword. But Dart doesn't have such legacy, so the keyword doesn't seem as well motivated.


> What is the difference between an async method and a method that returns a Future?

There's no difference for callers of the method, but you can only use await in methods that are async.

An async method can use await within it. A method can return a future but not be able to use the await keyword.

What is the point of this restriction? Why not just allow all Future-returning functions to use await?

Async methods also allow you to return values which will be returned as futures.

For example:

  return 4;
If the function is marked async, the return value should be Future<int>.

If you allow a non-async method use await, you get into this situation:

  Future<int> foo(){
    int result = await something ();
    // How to return "result" and still make compiler happy?

Why not just some generic function T -> Future<T>?

It sounds like the async keyword allows for a particular implicit type conversion within the function body, but on return statements only. That's a pretty thin justification for a keyword.

A typo:

    while (microTaskQueue.isNotEmpty){
It should be either "if { ... return }" or just "while { ... }".

Wow, I have no idea how similar Dart is to JavaScript.

Isolate == Worker Future == Promise

They touch on the importance of compiling to efficient JavaScript in their FAQ https://www.dartlang.org/faq#q-why-isnt-dart-more-like-haske...

But it probably also helps that one of dart's creators, Lars Bak also developed the V8 JavaScript engine.

For someone used to work with async/await only in C#, I was not expecting the execution model to be so fundamentally different in Dart.

Something to try to wrap my head around later!

Applications are open for YC Summer 2019

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact