async Future StreamSubscription Error - dart

Could someone please explain what's wrong with the following code. I'm making two calls to the function fInputData. The first works ok, the second results in an error :
"unhandled exception"
"Bad state: Stream already has subscriber"
I need to write a test console program that inputs multiple parameters.
import "dart:async" as async;
import "dart:io";
void main() {
fInputData ("Enter Nr of Iterations : ")
.then((String sResult){
int iIters;
try {
iIters = int.parse(sResult);
if (iIters < 0) throw new Exception("Invalid");
} catch (oError) {
print ("Invalid entry");
exit(1);
}
print ("In Main : Iterations selected = ${iIters}");
fInputData("Continue Processing? (Y/N) : ") // this call bombs
.then((String sInput){
if (sInput != "y" && sInput != "Y")
exit(1);
fProcessData(iIters);
print ("Main Completed");
});
});
}
async.Future<String> fInputData(String sPrompt) {
async.Completer<String> oCompleter = new async.Completer();
stdout.write(sPrompt);
async.Stream<String> oStream = stdin.transform(new StringDecoder());
async.StreamSubscription oSub;
oSub = oStream.listen((String sInput) {
oCompleter.complete(sInput);
oSub.cancel();
});
return oCompleter.future;
}
void fProcessData(int iIters) {
print ("In fProcessData");
print ("iIters = ${iIters}");
for (int iPos = 1; iPos <= iIters; iPos++ ) {
if (iPos%100 == 0) print ("Processed = ${iPos}");
}
print ("In fProcessData - completed ${iIters}");
}

Some background reading:
Streams comes in two flavours: single or multiple (also known as
broadcast) subscriber. By default, our stream is a single-subscriber
stream. This means that if you try to listen to the stream more than
once, you will get an exception, and using any of the callback
functions or future properties counts as listening.
You can convert the single-subscriber stream into a broadcast stream
by using the asBroadcastStream() method.
So you've got two options - either re-use a single subscription object. i.e. call listen once, and keep the subscription object alive.
Or use a broadcast stream - note there are a number of differences between broadcast streams and single-subscriber streams, you'll need to read about those and make sure they suit your use-case.
Here's an example of reusing a subscriber to ask multiple questions:
import 'dart:async';
import 'dart:io';
main() {
var console = new Console();
var loop;
loop = () => ask(console).then((_) => loop());
loop();
}
Future ask(Console console) {
print('1 + 1 = ...');
return console.readLine().then((line) {
print(line.trim() == '2' ? 'Yup!' : 'Nope :(');
});
}
class Console {
StreamSubscription<String> _subs;
Console() {
var input = stdin
.transform(new StringDecoder())
.transform(new LineTransformer());
_subs = input.listen(null);
}
Future<String> readLine() {
var completer = new Completer<String>();
_subs.onData(completer.complete);
return completer.future;
}
}

Related

Dart Socket one at a time

Hi i am building a blockchain and am trying to sync a list of connected peers,
but if the following code is called twice from different nodes the first call is still busy while the second call kicks in does anyone know how i could wait for the first message to be complete
class Peer {
Peer(
{required this.us,
required this.peers,
required this.allPeers,
required this.myPeers});
String us;
Map<String, int> peers;
Map<String, List<Online>> allPeers;
Map<String, List<Online>> myPeers;
List<String>? keep;
Map<String, List<NewNodePeerMessage>> nnpms = {};
//listen should never trigger a response to connect we will give the address / ip
// so your ndoe two will only sync when a thrid node joins the network
Future listen() async {
ServerSocket ss =
await ServerSocket.bind(us.split(':')[0], int.parse(us.split(':')[1]));
print('listening on ${us.split(':')[1]}');
// List<ReceivePort> lrp = [];
ss.listen((client) {
// ReceivePort rp = ReceivePort();
utf8.decoder.bind(client).listen((data) async {
final PeerMessage pm =
PeerMessage.fromJson(json.decode(data) as Map<String, dynamic>);
print('recieved msg from ${pm.from}');
switch (pm.type) {
case 'new-node':
{
// rp.listen((_) async {
final NewNodePeerMessage nnpm = NewNodePeerMessage.fromJson(
json.decode(data) as Map<String, dynamic>);
print('msg${nnpm.toJson()}');
peers[pm.code] ??= 3;
allPeers[pm.code] ??= [];
final List<Online> news = [];
print('apl ${allPeers[pm.code]!.length}');
// ,maby a list would let me know
// maby we should have a simple check to the loop bool that it doesnt start looping when it is adjusting the peers
print(
'allpeers before looping ${allPeers[pm.code]!.map((e) => e.toJson()).toList()}');
for (Online one in allPeers[pm.code]!.where((element) =>
element.address != pm.from &&
element.address != us &&
!nnpm.recieved.contains(element.address))) {
print(
'i am still loopin current one ${one.toJson()} current from ${pm.from}');
try {
final Socket ones = await Socket.connect(
one.address.split(':')[0],
int.parse(one.address.split(':')[1]));
nnpm.recieved.add(us);
nnpm.recieved.add(pm.from);
// if we would only move ones out of the for loop maby the program wont work at the same point in time
// we could have a bool that keeeps track of the msg is working on printstatement you inside off the msg
// so 8787 trigger 5442 because it has him in the list
// mabe a bool can be added to list if you is inished with listening
// isbusy knows iff its stuck in the loop if we write from here we are
// if we write ffrom connect we arent or this write could know if it is busy
// is busy shoudl be from down
// because if we wirte from here or we write from you is busy is true and false
// so if we write from below can isbusy stop us
// is isusy is true herewe can go into listen but on you we can not
ones.write(json.encode(NewNodePeerMessage(
isBusy: false,
max: peers[pm.code]!,
peer: nnpm.peer,
type: 'new-node',
from: us,
code: pm.code,
recieved: nnpm.recieved)
.toJson()));
print('propablywrote ${one.toJson()} from ${nnpm.from}');
ones.listen((ppmru) async {
print('listentedtoppmru ${one.toJson()}');
final PeersPeerMessageResponse ppmr =
peersPeerMessageResponse(ppmru);
print(
'abouttoaddnewonlines ${ppmr.onlines.map((e) => e.toJson())} and from ${ppmr.from}');
// news.add(Online(online: true, address: ppmr.from));
news.addAll(ppmr.onlines);
await ones.close();
});
} catch (err) {
one.online = false;
// break;
}
print('abouttoloopagain ${one.toJson()} from ${nnpm.from}');
}
print('gothereagainactuallydonelooping ${pm.from}');
// its actually that new node only should write to the client again only if its the first time maby
allPeers[pm.code]!.addAll(news);
allPeers[pm.code]!.removeWhere((element) => !element.online);
print(allPeers[pm.code]!.map((e) => e.toJson()).toList());
/// the problem occurs because of client clients response will shut down base or we could wrap it inside o try an catch
/// we need to know if this message will send the code up here or down to printstatement you to ones.listen or to s that listen
/// one global bool could say like will go down maby even when it goes up herte to printstatement abouttoaddnewonlines
/// how do we know here below that it will go to the you printstatement or down
/// so we need a message from up
allPeers[pm.code]!.add(Online(online: true, address: nnpm.peer));
// print('abouttowriteto ${client.address.address} ${client.port}');
print('abouttorespondto ${pm.from}');
client.write(json.encode(PeersPeerMessageResponse(
isBusy: true,
peer: nnpm.peer,
onlines: allPeers[pm.code]!
.where((element) => element.address != pm.from)
.toList(),
code: pm.code,
from: us)
.toJson()));
// await client.close();
// rp.sendPort.send(null);
// });
client.destroy();
break;
}
case 'new-node-through':
{
break;
}
case 'is-online':
client.write(null);
break;
case 'is-test':
print('recieved');
client.write('irespond');
break;
default:
break;
}
// client.destroy();
}, onDone: () {});
});
}
bool loop = false;
// Future connect(List<dynamic> args) async {
// is busy shoudl bee ffrom down
Future connect(String bootnode, String code) async {
final Socket s = await Socket.connect(
"${bootnode.split(':')[0]}", int.parse(bootnode.split(':')[1]));
print('connected to ${bootnode}');
s.write(json.encode(NewNodePeerMessage(
isBusy: true,
max: 3,
peer: us,
type: 'new-node',
code: code,
from: us,
recieved: []).toJson()));
print('befforelistening');
s.listen((pmmru) async {
print('whatwas first');
PeersPeerMessageResponse ppmr = peersPeerMessageResponse(pmmru);
print(ppmr.toJson());
allPeers[ppmr.code] ??= [];
allPeers[ppmr.code]!.addAll(ppmr.onlines);
allPeers[ppmr.code]!.add(Online(online: true, address: ppmr.from));
print(allPeers);
await s.close();
}, onDone: () {
print('doschopnescheee');
});
//because off up being triggered ffrom this msg we know it will go down because off client that write
// and client that write is up their and it m
// final somekindloop;
//if we would just never listen here would it relay on up might solve problem because up might be busy we could also ssst the isolate
// s.listen((ppmru) async {
// loop = true;
// // whenever we recieve here the ppmru could have the isbusy instead
// // print('you');
// // isBusy
// // while (!loop) {}
// PeersPeerMessageResponse ppmr = peersPeerMessageResponse(ppmru);
// print(ppmr.toJson());
// allPeers[ppmr.code] ??= [];
// allPeers[ppmr.code]!.addAll(ppmr.onlines);
// allPeers[ppmr.code]!.add(Online(online: true, address: ppmr.from));
// print(allPeers);
// await s.close();
// });
}
PeersPeerMessageResponse peersPeerMessageResponse(Uint8List resp) {
final PeersPeerMessageResponse ppm = PeersPeerMessageResponse.fromJson(
json.decode(String.fromCharCodes(resp).trim()) as Map<String, dynamic>);
return ppm;
}
// Future isOnline(String code) async {
// for (Online p in allPeers[code] ??= []) {
// try {
// final Socket peer = await Socket.connect(
// p.address.split(':')[0], int.parse(p.address.split(':')[1]));
// await peer.close();
// } catch (err) {
// p.ischis = false;
// }
// }
// }
}
its about the new node function inside of the switch statement while the first call is inside off the for loop the second call does not loop but increments the allPeers with new peers which because off the first call evolves into
Unhandled exception:
Concurrent modification during iteration: Instance(length:3) of '_GrowableList'.
#0 ListIterator.moveNext (dart:_internal/iterable.dart:336:7)
#1 WhereIterator.moveNext (dart:_internal/iterable.dart:438:22)
#2 Peer.listen.<anonymous closure>.<anonymous closure> (package:gov/peer/peer.dart:180:53)
<asynchronous suspension>
how can is use the on done event to wait or the first call to be complete?

When do Stream start publishing values to listeners?

After reading a bunch of documentation about Streams and StreamControllers in dart I tried to build a little example and was surprised of the results. All documentation I have read states that a stream starts emiting data as soon as a listener is registered. But this doesn't show any printed data:
class Order
{
String type;
Order(this.type);
}
class Pizza
{
}
void main()
{
Order order = Order("pzza");
final StreamController sc = StreamController();
sc.sink.add(order);
sc.sink.add(order);
sc.sink.add(new Order("pizza"));
Stream st = sc.stream.map((order) {
return order.type;
})
.map((orderType) {
if(orderType == "pizza")
return Pizza();
else
throw ("dude!, I don't know how to do that");
});
var sus = st.listen((pizza)
{
print("We did a pizza");
},
onError: (error)
{
print(error);
});
sus.cancel();
sc.sink.add(new Order("pizza2"));
}
I was expecting this output:
dude!, I don't know how to do that
dude!, I don't know how to do that
We did a pizza
When creating streams and adding data is all "sinked" data scheduled to be emited on the next application step?
Cheers.
You are right in that the documentation states that you listen on a stream to make it start generating events. However, streams are asynchronous so when you call the listen() method you are registering to receive events from the stream at some point in the future. Dart will then continue to run the remainder of your main function. Immediately after calling listen() you call cancel() to cancel the subscription which is why nothing is being printed.
If you remove or comment out the cancel and run it again you will see the expected output.
A slightly modified version of your code will hopefully highlight the run of events:
class Order {
String type;
Order(this.type);
}
class Pizza {}
void main() {
print("Main starts");
Order order = Order("pzza");
final StreamController sc = StreamController();
sc.sink.add(order);
sc.sink.add(order);
sc.sink.add(new Order("pizza"));
Stream st = sc.stream.map((order) {
return order.type;
}).map((orderType) {
if (orderType == "pizza")
return Pizza();
else
throw ("dude!, I don't know how to do that");
});
var sus = st.listen((pizza) {
print("We did a pizza");
}, onError: (error) {
print(error);
});
// sus.cancel();
sc.sink.add(new Order("pizza2"));
print("Main ends");
}
Running this produces the output:
Main starts
Main ends
dude!, I don't know how to do that
dude!, I don't know how to do that
We did a pizza
dude!, I don't know how to do that

Dart program exits without executing last statement

I'm trying to understand Streams and wrote some code.
Everything seems to work, the program exits with status code 0. But it doesn't print the 'loop done' and 'main done' strings. I can't figure out why.
import 'dart:async';
Stream<int> countStream(int to) async* {
for (int i = 1; i <= to; i++) {
yield i;
}
}
class Retry {
StreamController<int> _outgoing;
Retry(Stream<int> incoming) {
_outgoing = StreamController<int>();
_outgoing.addStream(incoming);
}
Future<void> process() async {
await for (final i in _outgoing.stream) {
print("got $i");
}
print('loop done'); // Not printed
}
}
void main() async {
var stream = countStream(4);
var retry = Retry(stream);
await retry.process();
print('main done'); // Not printed
}
The _outgoing.stream is never closed, so code after the await for will never execute. The VM does notice that there also won't be any new events on that stream so nothing else will ever happen, and it can exit. You could fix the bug with:
_outgoing.addStream(incoming).whenComplete(() {
_outgoing.close();
});

How to cancel a Stream when using Stream.periodic?

I'm having trouble canceling a stream that is created using the Stream.periodic constructor. Below is my attempt at canceling the stream. However, I'm having a hard time extracting out the 'count' variable from the internal scope. Therefore, I can't cancel the subscription.
import 'dart:async';
void main() {
int count = 0;
final Stream newsStream = new Stream.periodic(Duration(seconds: 2), (_) {
return _;
});
StreamSubscription mySubscribedStream = newsStream.map((e) {
count = e;
print(count);
return 'stuff $e';
}).listen((e) {
print(e);
});
// count = 0 here because count is scoped inside mySubscribedStream
// How do I extract out 'count', so I can cancel the stream?
if (count > 5) {
mySubscribedStream.cancel();
mySubscribedStream = null;
}
}
I'd rather use take(5) instead of checking > 5 and then cancel
final Stream newsStream = new Stream.periodic(Duration(seconds: 2), (_) => count++);
newsStream.map((e) {
count = e;
print(count);
return 'stuff $e';
}).take(5).forEach((e) {
print(e);
});

async.Future async.Completer - how to "continue" if an error

Some help with the following would be appreciated. I am writing some console test programs, and I want to be able to enter some parameters from the terminal (I don't want to use command line arguments - too many parameters). I have tried some variations, but I cannot find how to accomplish this. The following is the latest version of my test for terminal input. The problem with this program is that if an error is encountered, the Completer closes automatically, and I want to continue from either the Main() or from fGetNumber() function. While I can see why this program doesn't work, it illustrates what I need to achieve - re-enter the number, but I cannot find how to achieve that. If a valid number is entered, there is no problem. If an invalid number is entered, I cannot find out how to re-enter the number.
The code is as follows, and the problem I have is highlighted by "//////////" :
import "dart:async" as async;
import "dart:io";
void main() {
fGetNumber("Enter Nr of Iterations : ", 0, 999999)
.then((int iIters){
print ("In Main : Iterations selected = ${iIters}");
if (iIters == null) {
print ("In Main: Invalid Number of iterations : ${iIters}.");
} else {
fProcessData(iIters);
}
print ("Main Completed");
});
}
async.Future<int> fGetNumber(String sPrompt, int iMin, int iMax) {
print ("In fGetNumber");
int iIters = 0;
async.Completer<int> oCompleter = new async.Completer();
while (!oCompleter.isCompleted) { /////////// This loop does not work ///////
return fGetUserInput(sPrompt).then((String sIters) {
iIters = int.parse(sIters);
if (iIters < iMin || iIters > iMax) throw new Exception("Invalid");
oCompleter.complete(iIters);
return oCompleter.future;
}).catchError((_) => print ("Invalid - number must be from ${iMin} to ${iMax}")
).whenComplete(() => print ("fGetNumber - whenComplete"));// always gets here
}
print ("In fGetNumber (at end of function)"); //// it never gets here
}
async.Future<String> fGetUserInput(String sPrompt) {
print ("In fGetUserInput");
async.Completer<String> oCompleter = new async.Completer();
stdout.write(sPrompt);
async.Stream<String> oStream = stdin.transform(new StringDecoder());
async.StreamSubscription oSub;
oSub = oStream.listen((String sData) {
oCompleter.complete("$sData");
oSub.cancel();
});
return oCompleter.future;
}
void fProcessData(int iIters) {
print ("In fProcessData");
for (int iPos = 1; iPos <= iIters; iPos++ ) {
if (iPos%100 == 0) print ("Processed = ${iPos}");
}
print ("In fProcessData - completed ${iIters}");
}
// This loop does not work
Of course it does - you enter it exactly once, where you immediately return and therefore leave the loop and method.
// always gets here
That's because whenComplete() always gets called, on success or on error.
// it never gets here
Because you already returned out of the method.
So what can be done?
The easiest way would be to not rely on fGetUserInput(). Listen to stdin in fGetNumber and only complete the completer / cancel the subscription if the input is valid:
async.Future<int> fGetNumber(String sPrompt, int iMin, int iMax) {
print ("In fGetNumber");
async.Completer<String> oCompleter = new async.Completer();
stdout.write(sPrompt);
async.Stream<String> oStream = stdin.transform(new StringDecoder());
async.StreamSubscription oSub;
oSub = oStream.listen((String sData) {
try {
int iIters = int.parse(sData);
if (iIters < iMin || iIters > iMax) throw new Exception("Invalid");
oCompleter.complete(iIters);
oSub.cancel();
} catch(e) {
print("Invalid - number must be from ${iMin} to ${iMax}");
stdout.write(sPrompt);
}
});
return oCompleter.future;
}
Are there alternatives?
Of course. There are likely many, many ways to do this. This one for example:
async.Future<int> fGetNumber(String sPrompt, int iMin, int iMax) {
print ("In fGetNumber");
async.Completer<int> oCompleter = new async.Completer();
fGetUserInput(sPrompt, oCompleter, (String sIters) {
try {
int iIters = int.parse(sIters);
if (iIters < iMin || iIters > iMax) throw new Exception("Invalid");
return iIters;
} catch(e) {
print ("Invalid - number must be from ${iMin} to ${iMax}");
stdout.write(sPrompt);
}
return null;
});
return oCompleter.future;
}
void fGetUserInput(String sPrompt, async.Completer oCompleter, dynamic inputValidator(String sData)) {
print ("In fGetUserInput");
stdout.write(sPrompt);
async.Stream<String> oStream = stdin.transform(new StringDecoder());
async.StreamSubscription oSub;
oSub = oStream.listen((String sData) {
var d = inputValidator(sData);
if(d != null) {
oCompleter.complete(d);
oSub.cancel();
}
});
}
If you really feel there should be something addressed by the Dart team, you could write a feature request. But the Completer is designed to only be completed once. Whatever code you write, you can't just loop to complete it again and again.

Resources