How to create three infinity loop inside isolate? - dart

I am learning Dart and working with Isolate. I wrote next code, and expected that it will create three isolate process that will work infinity:
main() {
Isolate.spawn(echo, "Hello");
Isolate.spawn(echo, "Hello2");
Isolate.spawn(echo, "Hello3");
}
void echo(var message)
{
while(true)
{
print(message);
}
}
But I am getting very strange output like (every time different):
$ dart app.dart
Hello
Hello
Hello
Hello
HelloHello2
Hello
Hello3
Hello2
Hello

The VM will terminate the entire program as soon as the main isolate ends. For you, that happens after you have spawned all three isolates. There is nothing keeping the main isolate alive, so the entire program just ends ... eventually, when the isolate is done shutting down. When that is depends on timing, so it can vary quite a lot.
To keep an isolate alive forever, you can create a ReceivePort. Try addig:
var keepalive = ReceivePort();
to your program, then it should keep running forever.
Also, the printing is not just a list of lines containing hello's, they are intermixed.
The three isolates are running concurrently. They all write to the same output (stdout), so the outputs get intermixed. There is no promise that a print call is atomic, and it isn't, so a print call in one isolate can happen in the middle of a print call in another isolate.
What happens here is that print doesn't just print the argument, it also prints a newline afterwards. Those are two different writes to stdout, so it is possible for another isolate to print its message between the "Hello" and the "\n" following it.

Related

How do I access the value of the counter in a process?

In this program, I cannot for the life of me figure out how to access the value of the counter in a process.
-module(counter).
-export([start/0,loop/1,increment/1,value/1,stop/1]).
%% First the interface functions.
start() ->
spawn(counter, loop, [0]).
increment(Counter) ->
Counter ! increment.
value(Counter) ->
Counter ! {self(),value},
receive
{Counter,Value} ->
Value
end.
stop(Counter) ->
Counter ! stop.
%% The counter loop.
loop(Val) ->
receive
increment ->
loop(Val + 1);
{From,value} ->
From ! {self(),Val},
loop(Val);
stop -> % No recursive call here
true;
Other -> % All other messages
loop(Val)
end.
I assume it's:
{From,value} ->
From ! {self(),Val},
loop(Val);
which returns the value of the counter, but every time I use PID ! {PID,value}, or something similar to that it returns the thing after !, e.g. {<0.57.0>, value}.
TL;DR
You shouldn't use ! operator explicitly, it is considered an anti-pattern. You could run into some problems with typos in atoms or some bad data, just like you did this time.
To ensure correct communication with you one usually create some wrapper functions witch handle correct data format and communication with process. Function just like increment/1 value/1 and stop/1. In fact if you would use those, you would get expected results; in your case, assuming that PID is your counter, just call counter:value(PID).
Let me explain
There are few thing you seem to getting little bit wrong.
First of all ! will send message to another process. And that's all it does. Since everything in Erlang is expression (needs to return something, have a value) each call to ! will return right hand side of !. PID ! ok. will return ok, no matter what (there is slight chance that it will fail, but lets no go there). You send your message, and go on with your life, or execution.
Than, some process after receiving your message might decide to send you some message back. In case of {From, value} it will, in case of increment it wont. If you are expecting to get message back you need to wait for it and retrieve it from your mailbox. receive clause will do both waiting and retrieving. So if you decise to use ! on your own you should fallow it with receive with correct pattern match. You can see that value/1 function does just that.
Third thing is correct use of process ID's. I guess you started your counter correctly and you have it's Pid, and you can send messages to it with !. But if you expect it to send something back it needs to know your process ID, your address if you will. So you should have called PID ! {MyPid, values}. How to get MyPid? With self() function. Again, just like in value/1 function.
And last thing many people get wrong at the begging. counter module is just a file with some functions, it's not whole actor/process, and it's not an object. Fact that some value/1 and stop/1 are implemented in it, it doesn't mean that they will be run on counter actor/process. They are functions like any other, and if you call them they will be evaluated in your actor/process, on your stack (same goes for calling them from shell, shell is just another actor). You can spawn new process and tell it to run loop/1 function, but that's all it does. All increment/1 value/1 and stop/1 calls will be executed "on your side".
If this is somewhat confusing try to imagine some simpler function inside counter module, like
add(A, B) ->
A + B.
You can execute it from shell even without any counter process started. It will be created in your process, on your stack, it will add two numbers and return result.
This is important because when you call counter:value(Counter). it will execute Counter ! {self(),value}, "on your side", on your process, so self() will return Pid of your process, not the Pid of counter.
In theory you don't need to know this if you are just using those wrapper function (API or interface if you will), but since you are learning Erlang I would guess you will soon have to write such wrapper. Understanding what happens where is crucial then. Just remember that there is no magic in modules, no secret binding or special execution. Those are just plain old functions and they will be behaving just like in any other language. Only spawn, receive and maybe ! are little different.

Is my understanding of Dart's Future correct?

I'm learning Dart's Future, and have read some articles about the Future.
It says Dart is single-thread, and we can use Future to make some expensive functions run later, e.g. reading files.
Suppose reading a file will cost 10 seconds, and I have 3 files to read.
My dart code:
main() {
readFile("aaa.txt");
readFile("bbb.txt");
readFile("ccc.txt");
print("Will print the content of the files later");
}
readFile(String filename) {
File file = new File(filename);
file.readAsString().then((content) {
print("File content:\n");
print(content);
});
}
Since reading a file will cost 10 seconds, so the above code will cost at least 30 seconds, right? Using futures to read files just to make the expensive tasks run later one by one, without blocking current code, but won't reduce the total cost?
If in java, I can make a thread pool, and make 3 future tasks running in parallel, the total cost will be between 10 and 20 seconds.
Is it possible to do the same in Dart? Is using Dart's isolate the only solution?
I would expect that this could take 10 seconds, as it will start three reads, each of which will queue an callback to the "then" function when the read is complete. It is entirely possible that the three files will load in parallel and all complete after 10 seconds. The callbacks will be called on the main thread sequentially though.
Although the user code in dart is single threaded (assuming you don't use isolates or web workers), nothing says that the implementation can't create threads or use the operating system's asynchronous loading to perform tasks in parallel as long as the future's run sequentially in the main thread.
That's correct. If you start an new async path with new Timer(), new Future(), or scheduleMicrotask() it will be scheduled for later execution.
When one of your async paths is waiting for a network request or the file system returning data, another async path may jump in and run in the meantime. So you might get a runtime less than 30 seconds, but you can't reduce runtime by adding a CPU.
I have to admit, that I don't know details about when scheduling takes place and how it works exactly.
Dart has no threads, so if you want to run code in parallel you need isolates.
Almost 30 seconds.
I had just run the code with dart 2.16.2, and the result is almost 30 seconds.
here is my code:
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
main() async {
print('main start');
printCurrentTime("main before all future");
Future(() => readFile(0));
Future(() => readFile(1));
Future(() => readFile(2));
Future(() {
printCurrentTime("future last");
});
print('main end');
printCurrentTime("main");
}
printCurrentTime(String name) {
print("$name ${DateTime.now().millisecondsSinceEpoch}");
}
readFile(number) {
print("start read file $number");
var watch = Stopwatch();
watch.start();
var filename = r"path/to/file";
File file = File(filename);
file.readAsBytes().then((content) {
printCurrentTime("\nfuture#$number start");
print("File $number content:");
print(content.toString().length);
printCurrentTime("future#$number finish");
print("finish read file $number");
});
}
And here is the result:
main start
main before all future 1652964314276
main end
main 1652964314278
// all the event queue start to run
start read file 0
start read file 1
start read file 2
future last 1652964314290
// the dart system read file parallelly, after finish read file
// they put the future to the event queue, and dart start running all
// those event task one by one:
future#0 start 1652964314343
File 0 content:
241398625
future#0 finish 1652964317457
finish read file 0
future#1 start 1652964317457
File 1 content:
241398625
future#1 finish 1652964320470
finish read file 1
future#2 start 1652964320471
File 2 content:
241398625
future#2 finish 1652964323403
finish read file 2
As we can see:
file.readAsBytes() take about 53ms (or 100ms sometime during my test)
content.toString() take about 3s or more
So we can come to this conclusion:
file.readAsBytes() all run in the other thread parallelly, and the value the return Future<Uint8List> is added to the Event Task dequeue which is run synchronously, that's why we can see the future#1 start... print one by one.

Dart isolate call or send

I have a couple questions about how isolate works :
1) What is the difference between call and send and when I should use call over send?
2) Just curiosity, is there any way to chain isolate like we chain Future ?
3)
import 'dart:isolate';
echo() {
port.receive((msg, reply) {
print('I received: $msg');
});
}
main() {
var sendPort = spawnFunction(echo);
sendPort.call('Hello from main');
}
It displays : I received: Hello from main
but when I use send, it prints nothing, why?
Use the call() method on SendPort as a simple way to send a message and receive a reply. The call() method returns a Future for the reply. If you don't bother of the reply and simply want to send a message, use send().
Have a look at dart:isolate - Concurrency with Isolates for more informations.
For 3) it's explained in the above link :
In the standalone VM, the main() function runs in the first isolate (also known as the root isolate). When the root isolate terminates, it terminates the whole VM, regardless of whether other isolates are still running. For more information, see the section called “Keeping the root isolate alive”.

How do you run an interactive process in Dart?

The test below attempts to run the less pager command and return once
the user quits. The problem is that it doesn't wait for user input, it
just lists the entire file and exits. Platform: xubuntu 12.04, Dart
Editor build: 13049.
import 'dart:io';
void main() {
shell('less', ['/etc/mime.types'], (exitCode) => exit(exitCode));
}
void shell(String cmd, List<String> opts, void onExit(int exitCode)) {
var p = Process.start(cmd, opts);
p.stdout.pipe(stdout); // Process output to stdout.
stdin.pipe(p.stdin); // stdin to process input.
p.onExit = (exitCode) {
p.close();
onExit(exitCode);
};
}
The following CoffeeScript function (using nodejs I/O) works:
shell = (cmd, opts, callback) ->
process.stdin.pause()
child = spawn cmd, opts, customFds: [0, 1, 2]
child.on 'exit', (code) ->
process.stdin.resume()
callback code
How can I make this work in Dart?
John has a good example about how to look at user input. But doesn't answer your original question. Unfortunately your question doesn't fit with how Dart operates. The two examples you have, the Dart version and CoffeeScript/Node.js version, do two completely different things.
In your CoffeeScript version, the spawn command is actually creating a new process and then passing execution over to that new process. Basically you're program is not interactively communicating with the process, rather your user is interacting with the spawned process.
In Dart it is different, your program is interacting with the spawned process. It is not passing off execution to the new process. Basically what you are doing is piping the input/output to and from the new process to your program itself. Since your program doesn't have a 'window height' from the terminal, it passes all the information at once. What you're doing in dart is almost equivalent to:
less /etc/mime.types | cat
You can use Process.start() to interactively communicate with processes. But it is your program which is interactively communicating with the process, not the user. Thus you can write a dart program which will launch and automatically play 'zork' or 'adventure' for instance, or log into a remote server by looking at the prompts from process's output.
However, at current there is no way to simply pass execution to the spawned process. If you want to communicate the process output to a user, and then also take user input and send it back to a process it involves an additional layer. And even then, not all programs (such as less) behave the same as they do when launched from a shell environment.
Here's a basic structure for reading console input from the user. This example reads lines of text from the user, and exits on 'q':
import 'dart:io';
import 'dart:isolate';
final StringInputStream textStream = new StringInputStream(stdin);
void main() {
textStream.onLine = checkBuffer;
}
void checkBuffer(){
final line = textStream.readLine();
if (line == null) return;
if (line.trim().toLowerCase() == 'q'){
exit(0);
}
print('You wrote "$line". Now write something else!');
}

Is it possible to pass command-line arguments to a new isolate from spawnUri()

When starting a new isolate with spawnUri(), is it possible to pass command line args into that new isolate?
eg: Command line:
dart.exe app.dart "Hello World"
In app.dart
#import("dart:isolate");
main() {
var options = new Options();
print(options.arguments); // prints ["Hello World"]
spawnUri("other.dart");
}
In other.dart
main() {
var options = new Options();
print(options.arguments); // prints [] when spawned from app.dart.
// Is it possible to supply
// Options from another isolate?
}
Although I can pass data into other.dart through its SendPort, the specific use I want is to use another dart app that hasn't been created with a recievePort callback (such as pub.dart, or any other command-line app).
As far as I can tell the answer is currently no, and it would be hard to simulate via message passing because the options would not be available in main().
I think there are two good feature requests here. One is to be able to pass options on spawn() so that a script can run the same from the root isolate or a spawned isolate.
The other feature, which could be used to implement the first, is a way to pass messages that are handled by libraries before main() is invoked so that objects that main() depends on can be initialized with data from the spawning isolate.
Your example doesn't call print(options.arguments); in other.dart using the current stable SDK.
However
spanUri("other.dart");
spawns an Uri. So how about spawnUri("other.dart?param=value#orViaHash"); and try if you can find the param/value pair via
print(options.executable);
print(options.script);

Resources