node-style concurrent programming in dart?

59 views
Skip to first unread message

UGlee Ma

unread,
Dec 29, 2018, 12:48:34 AM12/29/18
to Dart Server-side and Cloud Development
I developed most programs on Node.js in recent years and am quite familiar with node concurrent programming stye, aka, event loop and pure asynchronous/non-blocking callback function. Though many people complains the callback hell, I personnally don't consider it as a problem at all.

Could dart do the similar thing? not the async/await or promise/futures, which are essentially an event driven programming encapsulated into (pseudo-) threads. I just need an event loop, non-blocking io, and callback-style asynchronous functions as node.js does. Could dart do this?


Anatoly Pulyaevskiy

unread,
Dec 29, 2018, 11:39:34 AM12/29/18
to UGlee Ma, Dart Server-side and Cloud Development
Dart runs in an event loop and all IO operations are non blocking, so I’d say it is very similar to Node in this regard.

Async/await in Dart is just a syntactic sugar on top of Futures and Streams. You don’t have to use it if passing callbacks for you is ok.

If you are looking for callback-based APIs like in Node.js then you probably won’t find much aside from those defined in Future and Stream classes. All built in libraries including dart:io rely on Futures and Streams instead of callbacks.

Of course you are free to define your own functions with callbacks, though you’re likely to interact with Futures inside your functions anyway.

I’d personally recommend spending some time to learn Futures and Streams in Dart as they are very much ubiquitous, especially in IO. These are much more powerful abstractions and can be used in variety of ways. It would also help you better understand code written by others, either it’s in the Dart SDK or in a community package from Pub.

Hope this helps.
On Fri, Dec 28, 2018 at 9:48 PM UGlee Ma <mati...@gmail.com> wrote:
I developed most programs on Node.js in recent years and am quite familiar with node concurrent programming stye, aka, event loop and pure asynchronous/non-blocking callback function. Though many people complains the callback hell, I personnally don't consider it as a problem at all.

Could dart do the similar thing? not the async/await or promise/futures, which are essentially an event driven programming encapsulated into (pseudo-) threads. I just need an event loop, non-blocking io, and callback-style asynchronous functions as node.js does. Could dart do this?



--
You received this message because you are subscribed to the Google Groups "Dart Server-side and Cloud Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cloud+un...@dartlang.org.
Visit this group at https://groups.google.com/a/dartlang.org/group/cloud/.

UGlee Ma

unread,
Dec 30, 2018, 8:00:52 AM12/30/18
to Dart Server-side and Cloud Development, mati...@gmail.com
Thank you very much for your reply.

I understand most people prefer the thread way to program concurrency and it is absolutely ok for a programming language to provide a threaded programming style even in an event loop context.

But I can tell a little more on my personal opinion on concurrent programming.

In process algebra, we can define two ways to compose two processes into one (just as addition or multiplication does to numbers in math): one is sequential composition, the other is concurrent composition.

In programming languages using traditional blocking-io style, the semicolon (';'), concatenating statements, is acturally a sequential composition. And if a concurrent composition is required, a new language primitive must be invented, such as threads, fibers or coroutines, and goroutines in golang.

However, as Robin Milner pointed out in his famous turing award lecture, if we must choose only one mathematical operation on process composition, either sequential or concurrent, which one should be chosen? The answer is the concurrent one. Milner said that a sequential composition could be represented by the concurrent composition of two processes, where one starts right after the other finishes.

This is exactly what the node callback does. An asynchronous function and it's callback function as the argument are acturally a sequential composition of two processes (in form of functions).

The semicolon between two "asynchronous" functions invoked synchronously one after the other, is acturally a symbol for concurrent composition.

This is the beauty of the node callback. There is no need to invent another language primitive to deal with concurrency. Or we may say, the event model is inherently immune to concurrency.

Of couse callback has its weakness in coding style. Acturally, callback itself is a **degenerated** event emitter, which just emit 'finish' event once. Writing an anonymous callback is much simpler than implementing a full-fledged class object that inherits from the Event Emitter. It also uses much less resources to run. But, event emitter is not just powerful, it is almighty for all concurrent problems in event-loop based execution context.

Supposing a group of processes starts simultaneously, and if one of them fails, all others should be cancelled. This is not a uncommon case in server side programming. In this case, if all processes are implemented as a full-fledged event emitter, all have the abort method, the clean-up in error handling is a charm. If things like this are composed to bigger and bigger processes repeatedly. I personally find that using Emitter with rith methods, such as abort, pause, resume, progress, etc, are the most simple, understandable and controllable way to programming concurrency. All actions are just jobs. Small jobs can be composed into larger one. Jobs can be pending, or started, paused and resumed, and aborted. In this way, the fine-grained behaviors, such as scheduling, laziness, queueing or error handling can be achieved in an extremely easy way. 

Since event emitters (jobs) are just objects, they can be composed and manipulated in all level of granularity. There is no different between a small job or a large job. But in threaded style programming. Functions are functions, threads are threads, coroutines are coroutines. They can't be converted to each other freely.

----

The concurrent programming can be described by just two orthogonal abstract concepts: processes, and inter-process communication.

In thread model, programming single process is easier and looks synchronous. But inter-process communication is a nightmire in complex scenario and looks asynchronous (possibly asynchronous inherently if  true thread are used).

In event model, all processes should be executed asynchronously (for node, the ideal is that all intensive computation should also be executed in libuv based thread poll), and the main process, the event loop itself, is acturally the synchronous communication between all processes.

This is the duality. Simply to say, each model just pick one to be synchronous and easy, and leave the other as asynchronous, to be, hard to deal with (thread model) or inefficient (event model, doing io asynchronous is OK but doing all computation asynchronously is very inefficent.)

----

So, I do hope Dart can provide the node style asynchronous io someday. After all, Dart is a language, which should provide the mechanism, not merely the policy. Futures or Promises or async/await, they are the solution to the coding style problem of sequential process composition. But they are definitely not an end-all, for-all solution to concurrent process composition. You cannot cancel, pause or resume a Future/Promise. A thread or thread-like things cannot be interrupted in nature. The only thing you can do is to poll some external state variables all the way, which is really disgusting and error prone.

The event emitter is the end-all, for-all solution, at least in the model level. And the callback is just a degenerated, simpler case. That is why I almost never write async/await in top level composition. I do use them in the most fine grained level of behavior. Then they are converted to callback or encapsulated into event emitters for higher level composition.

----

And since I am so happy with Node, why did I come here?

For JavaScript, you can happily write and run it if memory usage is not a concern. But if the memory is a concern, for example, if the arm/linux board has merely 16M~64M ram, Node is not practical any more for a medium-sized application.

I love JavaScript. And I have read some tutorials online about Dart. Glad to know that almost all the good things in JavaScript, such as the first-class functions, closures, are preserved in Dart. Futhurmore, Dart program can be compiled and has a type system, which usually means the memory could be used more efficiently.

IMHO, among the competition between programming languages, a language is popular not because it is designed better than others. it is because at certain moment, there is a new requirement to program something and all other existing language cannot do it very well. Dart is simple and light-weight. Could be run efficiently with limited memory. So I do think in the incomming IoT era, there will be a huge success for Dart, where python and JavaScript cannot do efficiently with limited system resources.

But please, besides future-based dart.io, provides node-style callback-based aio to programmers and let us to choose which one to use. 

If you never heard server-side programmer complains this, I suppose that in most case of server programming, they rely on the database for data persistence and the dynamic, horizontal expansion of the virtual hosts to deal with the short of computing resources. In IoT, however, this is not the case. Most device has very limited computation power and io capabilities. When the incomming tasks flooding, the best thing we can do is to provide the **partial** usability of the service by scheduling, queueing, and rejecting or aborting unimportant jobs. This is the lesson we learned for years of programming an extremely low-end home NAS device. No databas, no way to expand the computing resources. This is where the node model shines. Everything can be scheduled in a simple and flexible way. I do think this is crucial for most IoT devices.

Günter Zöchbauer

unread,
Dec 30, 2018, 8:20:10 AM12/30/18
to Dart Server-side and Cloud Development, mati...@gmail.com
As Anatoly already explained, Dart is single-threaded and callback based.
The difference is that with Future (single callback) and Stream (series of callbacks) the callback API is unified and with improved composability.
This also allows `async`/`await` to be used for all callback based APIs if you prefer.
As far as I know JS/node.js provide similar functionality with Promise and Observable.

To utilize additional threads you can start up additional isolates which have their own event queue each. 

Anatoly Pulyaevskiy

unread,
Dec 30, 2018, 11:10:52 AM12/30/18
to Günter Zöchbauer, Dart Server-side and Cloud Development, mati...@gmail.com
Check out StreamController in Dart SDK. It is Dart’s version of EventEmitter. You can add events, be notified when listeners join or leave, pause and resume emitting of events and everything else you described about EventEmitter.

The main difference with Node is that in Dart instead of subscribing to events on StreamController itself we have a notion of Stream which is its own class. StreamController simply provides access to the stream it controls. Users than subscribe to events on the stream by simply calling listen(callback).

Now this is where power of Dart’s Streams begins. Streams provide quite a few methods to transform them. You can map, filter, combine streams in variety of ways. There is really no limit to what you can do with streams and it would take you just a few lines of code.
You can go full functional style with community package rxdart, which adds even more methods like debounce and such.

One more difference to Node’s EventEmitter is that Streams essentially produce only one event type which is similar to on(“data”, callback) in Node (well technically there are two types, the second is for errors).

Important advantage of Dart over JS is that all of these APIs are designed with type safety in mind, so you catch errors earlier and in most cases type system will tell you if you did something wrong before you notice it.

As Gunter mentioned Dart is single threaded but if you need to perform computationally expensive task and not block main application “thread” you can use Isolates which are similar to threads but with no shared memory. Isolates can communicate with each other using message passing.

Each isolate runs its own event loop so you have full non-blocking access to outside world. There is no need in libuv or anything else 3rd party.

I’d recommend you to give it a try and see for yourself. Find an example EventEmitter and try to implement it using StreamController and Streams in Dart.

UGlee Ma

unread,
Dec 31, 2018, 6:34:56 AM12/31/18
to Dart Server-side and Cloud Development, gzo...@gmail.com, mati...@gmail.com
Thank you very much, and also thanks to Gunter. You are very kind and patient people. I'm going to get hands wet. I will be back if I'm frustrated again.

matianfu
Reply all
Reply to author
Forward
0 new messages