Advantage of use of isolate over async in Dart - dart

I'm learning isolate in Dart, reading the official tutorial. I don't understand, however, what the advantage of isolate over async is.
To see how isolates work, I wrote this simple code:
import 'dart:isolate';
import 'dart:math';
//generates many random numbers and returns the final result
Future<int> f({required int seed}) async {
const numIter = 100000000;
const upperBound = 10000;
var r = Random(seed);
for (var i = 0; i < numIter; ++i) {
r.nextInt(upperBound); //just discards the result
}
return r.nextInt(upperBound);
}
void main() async {
var sw = Stopwatch();
sw.start();
final future1 = f(seed: 0);
// final future2 = Isolate.run(() async => f(seed: 1));
// final future2 = f(seed: 1);
print(await future1);
// print(await future2);
sw.stop();
print(sw.elapsedMilliseconds.toString() + "(ms)");
}
The output is 685(ms).
If I un-comment these two lines,
// final future2 = f(seed: 1);
// print(await future2);
the output becomes 1338(ms), which is approximately the double of the previous 685(ms). Since dart is single-threaded (has only main isolate by default), this is expected. So far so good.
Then, if I instead un-comment these two lines,
// final future2 = Isolate.run(() async => f(seed: 1));
// print(await future2);
the output becomes 1304(ms). Why? I expect the output to stay around 600(ms) because Isolate.run() introduces another thread of execution.
Environment: M1 Macbook. The code is run via dart run command.

#jamesdlin is correct. Because f() is not actually asynchronous, the statement final future1 = f(seed: 0); blocks the main isolate before the second isolate is created.
You can see this with:
import 'dart:isolate';
import 'dart:math';
//generates many random numbers and returns the final result
Future<int> f({required int seed}) async {
const numIter = 100000000;
const upperBound = 10000;
var r = Random(seed);
for (var i = 0; i < numIter; ++i) {
r.nextInt(upperBound); //just discards the result
}
return r.nextInt(upperBound);
}
void main() async {
var sw = Stopwatch();
sw.start();
final future1 = f(seed: 0);
print(sw.elapsedMilliseconds.toString() + "(ms)");
final future2 = Isolate.run(() async => f(seed: 1));
print(await future1);
print(await future2);
sw.stop();
print(sw.elapsedMilliseconds.toString() + "(ms)");
}
Which outputs:
962(ms)
1124
4512
1946(ms)
The following modification will behave more along the lines of what you're expecting:
import 'dart:isolate';
import 'dart:math';
//generates many random numbers and returns the final result
int f({required int seed}) {
const numIter = 1000000000;
const upperBound = 10000;
var r = Random(seed);
for (var i = 0; i < numIter; ++i) {
r.nextInt(upperBound); //just discards the result
}
return r.nextInt(upperBound);
}
void main() async {
var sw = Stopwatch();
sw.start();
final future1 = Isolate.run(() => f(seed: 0));
final future2 = Isolate.run(() => f(seed: 1));
print(await future1);
print(await future2);
sw.stop();
print(sw.elapsedMilliseconds.toString() + "(ms)");
}

Related

Dart : what happens when two or more tasks are waiting on the same Future

In Dart, when two or more tasks are waiting on the same Future, when the Future completes, do the tasks get notified/run in the order that they did the await i.e. the first to do an await is the first to run.
Is this code guaranteed to output 2
int res = 0;
Future<void> foo1 () async
{
await Future.delayed(Duration(seconds: 2));
res = 2;
}
void main() async
{
await foo1();
print(res);
}
and what about this code, slightly less obvious
int res = 0;
Future<void> foo1 () async
{
await Future.delayed(Duration(seconds: 2));
}
Future<void> foo2 (Future<void> f1) async
{
await f1;
res = 2;
}
Future<void> foo3 (Future<void> f1) async
{
await f1;
res = 3;
}
void main() async
{
res = 0;
Future<void> f1 = foo1();
foo3(f1);
foo2(f1);
await f1;
print(res);
}
There is no guarantee that the callbacks get called in the order they were added.
The dart:async library code tries to call the callbacks in that order, but it's best-effort only. There are cases, typically where one callback is added before the future is completed, and the other is added after, where it's possible to get called in a different order, because they are handled by different code paths.
It's not easy to trigger those cases, the timing has to be just right, but it is possible.
In the example here, there are three awaits on the f1 future. It's most likely that the printed value will be 2 (because nothing fancy is happening), but both 3 and 0 are allowed results.
Code printed 2.
int res = 0;
Future<void> foo1() async {
await Future.delayed(const Duration(seconds: 2));
print('foo1 method res 1: $res');
res = 2;
print('foo1 method res 2: $res');
}
void main() async {
await foo1();
print('last res: $res');
}
🧑‍💻 Code Test Output
foo1 method res 1: 0
foo1 method res 2: 2
last res: 2

How can I make it work as an async function?

I expect the 'Second' would print out before then the 'First', but actually not. How can I make it work as an async sequence?
Future first() async{
for (int i = 0; i < 10000000000; i++) {}
print('First');
}
void second() {
print('Second');
}
main() async{
await first();
second();
}

async* blocking function body from executing

When adding async* to listen method it isn't executing the function body
import 'dart:async';
main(List<String> args) {
print('====');
tranStream();
}
Stream<int> intStreamer() async* {
int c = 0;
while (c <= 30) {
await Future.delayed(Duration(seconds: 1));
yield c++;
}
}
tranStream() {
intStreamer().listen((event) async* { // If i remove async* from here it will execute print statement
print(event);
});
}
If i remove async* from intStreamer().listen it will execute print statement. What is happening here?
When you are using async*, the method will only start being executed when the returned Stream gets a subscriber. Your code does not really make any sense since listen takes a method which returns void. So nobody are going to listen on the returned Stream which the given method will automatically return (based on the async* keyword).
I would also properly rewrite your code so you instead of listen uses await for which I think makes it more clear what happens`:
import 'dart:async';
Future<void> main(List<String> args) async {
print('====');
await tranStream();
}
Stream<int> intStreamer() async* {
int c = 0;
while (c <= 30) {
await Future<void>.delayed(const Duration(seconds: 1));
yield c++;
}
}
Future<void> tranStream() async {
await for (final event in intStreamer()) {
print(event);
}
}
Update with example of tranStream returns a Stream:
import 'dart:async';
Future<void> main(List<String> args) async {
print('====');
await for (final event in tranStream()) {
print('main got: $event');
}
}
Stream<int> intStreamer() async* {
int c = 0;
while (c <= 30) {
await Future<void>.delayed(const Duration(seconds: 1));
yield c++;
}
}
Stream<int> tranStream() async* {
await for (final event in intStreamer()) {
print('tranStream got: $event');
yield event;
}
}

Out of Memory error when implementing breadth first search algorithm

I got the following error:
[error][1] (E/DartVM (14988): Exhausted heap space, trying to allocate 536870928 bytes.
E/flutter (14988): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: Out of Memory)
When trying to implement a breadth_first search algorithm to find the shortest path in a graph. I found the algorithm written in C# and I am trying to rewrite it in dart/flutter.
The original code in C# can be found here.
My dart code:
import 'dart:collection';
import 'package:stack/stack.dart';
class Node<T>{
int id;
Node(this.id);
String toString() => '$id';
}
class Graph<T>{
final Map<Node, List<Node>> adj;
Graph(this.adj);
void AddEdge(Node node1,Node node2){
if(!adj.containsKey(node1))
adj[node1]=List<Node>();
if(!adj.containsKey(node2))
adj[node2]=List<Node>();
adj[node1].add(node2);
adj[node2].add(node1);
}
Stack<Node> ShortestPath(Node source, Node dest){
var path=Map<Node<T>,Node<T>>();
var distance=Map<Node<T>,int>();
//adj.keys.forEach(( node) => distance[node]=-1);
for(var node in adj.keys){
distance[node]=-1;
}
distance[source]=0;
var q=Queue<Node<T>>();
q.add(source);
while(q.isNotEmpty){
var node=q.removeFirst();
for(var adjs in adj[node].where((n) => distance[n]==-1)){
distance[adjs]=distance[node]+1;
path[adjs]=node;
q.add(adjs);
}
}
var res=Stack<Node>();
var cur=dest;
while(cur != res){
res.push(cur);
cur=path[cur];
}
res.push(source);
return res;
}
}
void main() {
var g = new Graph({});
var n1 = new Node<int>(1);
var n2 = new Node<int>(2);
var n3 = new Node<int>(3);
var n4 = new Node<int>(4);
var n5 = new Node<int>(5);
var n6 = new Node<int>(6);
var n7 = new Node<int>(7);
g.AddEdge(n1, n2);
g.AddEdge(n1, n3);
g.AddEdge(n1, n4);
g.AddEdge(n4, n5);
g.AddEdge(n2, n6);
g.AddEdge(n4, n7);
g.AddEdge(n5, n6);
g.AddEdge(n6, n7);
var answ=g.ShortestPath(n1, n7);
print(answ);
}
So what is the wrong with my program, and if anyone know better way to find shortest path in graph to use it in dart it will be great.
Thanks in advance
First, you main problem is properly that your while loop is not correct according to the C# implementation:
var res=Stack<Node>();
var cur=dest;
while(cur != res){
res.push(cur);
cur=path[cur];
}
res.push(source);
return res;
This loop will never finish res and cur are entirely different types where cur are a Node and res are a Stack. If you check the C# implementation you can see this is not correct:
var res = new Stack<Node<T>>();
var cur = dest;
while(cur != source) {
res.Push(cur);
cur = path[cur];
}
res.Push(source);
return res;
So I think by comparing against source it will properly solve the problem. But there are a lot of smaller problems in you code where types are not really great and where you could make it a lot more type safe by using generics more places.
I have therefore added more typing information to you code (which I needed to do to catch the type error). I have also dropped the usage of the Stack class since I don't think it makes much sense. Also, the Stack class you got had no toString implementation so I just thought it was easier to just use a List and return that as the result:
import 'dart:collection';
class Node<T> {
int id;
Node(this.id);
#override
String toString() => '$id';
}
class Graph<T> {
final Map<Node<T>, List<Node<T>>> adj;
Graph(this.adj);
void AddEdge(Node<T> node1, Node<T> node2) {
if (!adj.containsKey(node1)) adj[node1] = <Node<T>>[];
if (!adj.containsKey(node2)) adj[node2] = <Node<T>>[];
adj[node1].add(node2);
adj[node2].add(node1);
}
List<Node<T>> ShortestPath(Node<T> source, Node<T> dest) {
final path = <Node<T>, Node<T>>{};
final distance = <Node<T>, int>{};
//adj.keys.forEach(( node) => distance[node]=-1);
for (final node in adj.keys) {
distance[node] = -1;
}
distance[source] = 0;
final q = Queue<Node<T>>();
q.add(source);
while (q.isNotEmpty) {
final node = q.removeFirst();
for (final adjs in adj[node].where((n) => distance[n] == -1)) {
distance[adjs] = distance[node] + 1;
path[adjs] = node;
q.add(adjs);
}
}
final res = <Node<T>>[];
var cur = dest;
while (cur != source) {
res.add(cur);
cur = path[cur];
}
res.add(source);
return res;
}
}
void main() {
final g = Graph<int>({});
final n1 = Node<int>(1);
final n2 = Node<int>(2);
final n3 = Node<int>(3);
final n4 = Node<int>(4);
final n5 = Node<int>(5);
final n6 = Node<int>(6);
final n7 = Node<int>(7);
g.AddEdge(n1, n2);
g.AddEdge(n1, n3);
g.AddEdge(n1, n4);
g.AddEdge(n4, n5);
g.AddEdge(n2, n6);
g.AddEdge(n4, n7);
g.AddEdge(n5, n6);
g.AddEdge(n6, n7);
final answ = g.ShortestPath(n1, n7);
print(answ); // [7, 4, 1]
}

StreamTransformer in Dart being skipped?

I'm not sure if I'm not understanding this correctly, but here's my code. I'm trying to get the StreamTransformer to act on the stream, but the values still come out the other end untouched.
Note: I added the .map() function, which does nothing, just to make sure it wasn't a missing map function that was my issue. I'm leaving it here just in case.
import 'dart:async';
void main() {
int count = 0;
var counterController = new StreamController();
counterController.stream.listen((value) => print(value));
void increment() {
counterController.add(count++);
}
final transformToString =
new StreamTransformer.fromHandlers(handleData: (number, sink) {
if (number.runtimeType == int) {
sink.add("The counter is at $number!");
} else {
sink.addError("$number is not an int!");
}
});
counterController.stream.map((input) => input).transform(transformToString);
for(int i=0; i < 10; i++){
increment();
}
}
Link to the code in DartPad
As was mentioned by my instructor, the transform function creates out a new stream. So I have to attach a listener to the transformed stream, I can't expect transformed values to come out of the old stream. So the modified code below works.
import 'dart:async';
void main() {
...
counterController.stream.map((input) => input)
.transform(transformToString).listen(print);
for(int i=0; i < 10; i++){
increment();
}
}

Resources