Update: Coincidentally, Flutter team just released a clip. It illustrates non-blocking concepts visually beautiful. In the article below, it is claimed that Dart is NOT a single-threaded, while this video claims otherwise. I will leave the article as is while researching and discussing more.
TLDR: Flutter/Dart is NOT single-threaded; Dart’s concurrency model is NOT Java’s thread; Future/Async/Await runs on the same thread and solves IO-bound problems, while Dart’s Isolate/Flutter’s
compute runs on a different isolated (no-shared-memory) thread and solves CPU-bound ones.
Myth #1: Flutter/Dart is single-threaded
All over the internet, I see people talking Dart is a single-threaded language, and thus so is Flutter. I would argue that saying Dart is a single-threaded language is not technically true. Let me break it down.
It is true that any piece of Dart code is executed in a single thread. That is to say, any piece of code, having no callback and
await keyword, is guaranteed to be executed uninterrupted.
However, in my opinion, a single-threaded language is an old kind of programming language that cannot take advantage of multi-core processors. For the sake of simplicity, let us say single-threaded language is the opposite of concurrent language (although concurrency is not parallelism). In other words, on multi-core processors, applications written properly in a concurrent language can do several computations in parallel and make use of multi-core, while those written in single-threaded language perform no better than those running on single-core.
Dart actually supports multi-threading (kind of, see next myth) using Isolates, and with this, Flutter can take full advantage of modern multi-core processor on mobile or desktop.
Myth #2: Flutter/Dart concurrency model is the same as Java’s thread
isolates [are] independent workers that are similar to threads but don’t share memory, communicating only via messages.
Some people, when confronted with a problem, think, “I know, I’ll use threads,” and then two they hav erpoblesms.
Because Dart is using message passing pattern and there is no shared memory/variables, there is hardly any need for lock or mutex. It is also noted that
You may experience errors if you try to pass more complex objects, such as a
In simpler words, most of the time, communication among Dart’s
isolates must be serializable.
Myth #3: Future/Async/Await runs code in separate threads and solves all blocking problems
Totally not true. Future/Async/Await runs the code in the same thread. If there is a need to wait for external data/events, Dart’s event queue shall be used, but still in the same thread. Thus, Future/Async/Await only solves half of the blocking problems.
To have a better understanding, we should take a step back. Blocking in programming can be grouped into 2 categories, I/O-bound and CPU-bound.
I/O refers to the data in motion, for example, network HTTP request/response, reading/writing data to local storage, message passing among threads/
isolates (see Myth #2). The waiting, for example, when we send out an HTTP request and wait for the HTTP response, can be several seconds, and in that several seconds, our app can be blocked and UI can be frozen if not written properly.
In Java/Kotlin or ObjC/Swift, by default, most of IO-bound operations are blocking, and developers must consciously do some extra code to have it “off” the “main thread” with various techniques. In Dart, by default, all IO-bound operations are async and return
Future. Developers can opt-in to use blocking variations by appending
Sync prefix, such as
However, Future/Async/Await does not unblock CPU-bound operations. Take a look at this simple example
In this case, using Future/Async/Await actually makes the performance much much worse, like 180x worse (oh and be careful when tweaking the param,
veryLongRunningCpuBoundFunction is designed to have
O(nⁿ) time complexity 😈), but does not solve the blocking problem. If you can’t believe, simply copy-paste
veryLongRunningCpuBoundFunction (either blocking or async version) and run in your Flutter app, your UI will completely be frozen as soon as the function is invoked.
veryLongRunningCpuBoundFunction may seem unrealistically simple and/or stupid. In practice, common CPU-bound operations are:
- matrix multiplication
- cryptography-related (such as signing, hashing, key generation)
- image/audio/video manipulation
- offline machine learning model computation
- compression (such as zlib)
- Regular expression Denial of Service — ReDoS
In such cases, we should use the tool we have been discussing in the previous 2 myths,
Isolate API, in my opinion, is not the easiest to use. Fortunately, Flutter has a utility function,
compute which is basically a wrapper around Dart’s Isolate with much simpler (but somewhat limited) API. In fact, the example from Flutter is about parsing JSON in the background (parsing JSON is categorized under serialization/deserialization case of CPU-bound operations). It’s also worth noticing that
compute returns a
Future, because the CPU-bound code (parsing JSON) is now running in a different isolate, and isolate communication (message passing) is IO-bound.
Revisiting the example above, here’s the correct way to run the code without having UI frozen.
Again, it’s worth noticing that
compute returns a
veryLongRunningCpuBoundFunction itself returns a primitive data type (in this case
computeis an extremely important point for people coming to Flutter from iOS/Android background.
Isolate / Flutter’s
compute is an extremely important point for people coming to Flutter from iOS/Android background. As explained above, iOS/Android traditionally do not distinguish between IO-bound and CPU-bound blocking operations, thus developers always consciously and manually use the same techniques to deal with any blocking operations (threading + Future/Callback/Stream on Android, GCD on iOS). In Dart/Flutter, all IO-bound blocking operations have already been identified and solved by the language with
Future, but newcomers should be aware of Dart’s
Isolate / Flutter’s
compute for computational-heavy operations to avoid UI frozen.
Flutter/Dart is not technically single-threaded, even though Dart code is executed in a single thread. Dart is a concurrent language with message passing pattern, that can take full advantage of modern multi-core architecture, without worrying about lock or mutex. Blocking in Dart can be either I/O-bound or CPU-bound, which should be solved, respectively, by
Future and Dart’s
This article is free, and so is your clap👏. Did you know you can press the clap👏 button 50 times?