How can I await a stream (or any other event queue) multiple times?
I tried Stream.first & Stream.single, both doesn't work.
What I want to do:
//next is a fake member
Future<void> xxx() async {
final eventStream = foo();
await eventStream.next; // wait for first event
//do some work
await eventStream.next; // wait for second event
//do some other differnet work
await eventStream.next; // wait for 3rd event
// another differnet work
return;
}
equvalent to:
Future<void> xxx() async {
final eventStream = foo();
int i=0;
await for (final _ in eventStream){
if(i==0);//do some work
else if(i==1);//do some other differnet work
else if(i==2){;break;}//another differnet work
++i;
}
return;
}
Try StreamQueue from package:async.
var q = StreamQueue(eventStream);
var v1 = await q.next;
var v2 = await q.next;
var v3 = await q.next;
// other work.
await q.cancel(); // If not listened to completely yet.
I end up with a custom class
class CompleterQueue<T> {
final _buf = <Completer<T>>[];
var _iWrite = 0;
var _iRead = 0;
int get length => _buf.length;
Future<T> next() {
if (_iRead == _buf.length) {
_buf.add(Completer<T>());
}
final fut = _buf[_iRead].future;
_iRead += 1;
_cleanup();
return fut;
}
void add(T val) {
if (_iWrite == _buf.length) {
final prm = Completer<T>();
_buf.add(prm);
}
_buf[_iWrite].complete(val);
_iWrite += 1;
_cleanup();
}
void _cleanup() {
final minI = _iWrite < _iRead ? _iWrite : _iRead;
if (minI > 0) {
_buf.removeRange(0, minI);
_iWrite -= minI;
_iRead -= minI;
}
}
}
Related
I'm having some trouble writing and reading from a socket multiple times. I'm writing a speed test and I essentially want to write an http request using a socket, which will return garbage data which I then read to test the bandwidth speed. I've made the http request successfully, but I'm unable to flip flop between writing to the socket and then reading the response. I can read the data from the socket's stream once, and then it doesn't seem to have any more data to read even after i've made another request.
The salient part of the code is in the start method where the loop runs:
class DownloadTest {
late Socket socket;
late Stream<Uint8List> dataStream;
bool graceTimeOver = false;
int totalBytesDownloaded = 0;
late DateTime startTime;
final ckSize = 10;
final graceTime = 2;
final dlTime = 5;
final client = HttpClient();
final String _serverAddress;
final void Function(double mbps) onProgress;
DownloadTest({required serverAddress, required this.onProgress})
: _serverAddress = serverAddress;
Future<void> start() async {
await resetTest();
while (true) {
writeDlRequest();
var bytesDownloaded = await downloadData();
print(graceTimeOver);
if (!graceTimeOver) {
checkGraceTime();
} else {
print(bytesDownloaded);
totalBytesDownloaded += bytesDownloaded;
if (testFinished()) {
break;
}
}
}
print('test over');
await socket.close();
}
Future<int> downloadData() async {
var bytes = 0;
await for (var data in dataStream) {
bytes += data.length;
}
return bytes;
}
Future<void> resetTest() async {
socket = await Socket.connect(_serverAddress, 80);
dataStream = socket.asBroadcastStream();
graceTimeOver = false;
startTime = DateTime.now();
totalBytesDownloaded = 0;
}
void checkGraceTime() {
if (!graceTimeOver) {
var elapsedSeconds =
startTime.difference(DateTime.now()).inMilliseconds / 1000;
if (elapsedSeconds >= graceTime) {
graceTimeOver = true;
startTime = DateTime.now();
print('grace time over');
}
}
}
void writeDlRequest() {
socket.write('GET /garbage.php?ckSize=$ckSize HTTP/1.1\r\n');
socket.write('Host: speedtest.somethingsomething.com:80\r\n');
socket.write('Connection: keep-alive\r\n');
socket.write('\r\n');
}
bool testFinished() {
var elapsedSeconds =
startTime.difference(DateTime.now()).inMilliseconds / 1000;
return elapsedSeconds >= dlTime;
}
}
I've made the socket's stream into a broadcast stream so I should be able to listen to it multiple times, but after reading from it the first time, it fails to read any more data and the loop just spins. Any ideas where i'm going wrong?
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);
});
Based on the SharedPreferences class, I try to retrieve a preference value like so:
String loadIPAddress() {
SharedPreferences.getInstance().then((SharedPreferences prefs) {
try {
var loadedValue = prefs.getString('serverIPAddress');
print('loadIPAddress <= ' + loadedValue);
return loadedValue; // [1]
} catch (e) {
print('loadIPAddress <= NOPE');
return '---'; [2]
}
});
}
Unfortunately, this doesn't return a value each time.
Q: Does the 1 and [2] return statements return the value of loadIPAddress()?
No, as you've guessed, those returns return from the then callback.
To return from loadIPAddress, refactor it like this:
Future<String> loadIPAddress() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
try {
var loadedValue = prefs.getString('serverIPAddress');
print('loadIPAddress <= ' + loadedValue);
return loadedValue;
} catch (e) {
print('loadIPAddress <= NOPE');
return '---';
}
}
Note that having made loadIPAddress async, it now returns a Future, so you should call it like:
String ip = await loadIPAddress();
// or
loadIPAddress().then((String ip) {
// do something with ip - probably setState
});
In the following code I thought the f1 > f2 > f3 would be the invocation order, but only f1 is being invoked. How can I get the 3 functions to be invoked sequentially?
I have added the following to the main function and it works as expected but I want to know if there are other definitive ways of achieving the same results?
print('Starting main');
List<Future> futures=new List<Future>();
Future v1=f1();
Future v2=f2();
Future v3=f3();
futures.add(v1);
futures.add(v2);
futures.add(v3);
Future.wait(futures);
print('Leaving main');
import 'dart:async';
Duration d1 = new Duration(seconds: 5);
Duration d2 = new Duration(seconds: 10);
Duration d3 = new Duration(seconds: 15);
bool r1 = false;
bool r2 = false;
bool r3 = false;
void cb1() {
print('Entering CB1');
r1 = true;
print('Leaving CB1');
}
void cb2() {
print('Entering CB2');
r2 = true;
print('Leaving CB2');
}
void cb3() {
print('Entering CB3');
r3 = true;
print('Leaving CB3');
}
Timer t1;
Timer t2;
Timer t3;
Future<bool> start1() {
print('Entering start1');
Completer<bool> r = new Completer();
r.future.then((_) {
while (!r1) {
}
print('Completing start1');
r.complete(true);
});
print('Leaving start1');
return r.future;
}
Future<bool> start2() {
print('Entering start2');
Completer<bool> r = new Completer();
r.future.then((_) {
while (!r2) {
}
print('Completing start2');
r.complete(true);
});
print('Leaving start2');
return r.future;
}
Future<bool> start3() {
print('Entering start3');
Completer<bool> r = new Completer();
r.future.then((_) {
while (!r3) {
}
print('Completing start3');
r.complete(true);
});
print('Leaving start3');
return r.future;
}
Future<bool> f1() {
print('Entering f1');
Completer<bool> result = new Completer();
t1 = new Timer(d1, cb1);
result.complete(start1());
print('Leaving f1');
return result.future;
}
Future<bool> f2() {
print('Entering f2');
Completer<bool> result = new Completer();
t2 = new Timer(d2, cb2);
result.complete(start2());
print('Leaving f2');
return result.future;
}
Future<bool> f3() {
print('Entering f3');
Completer<bool> result = new Completer();
t3 = new Timer(d3, cb3);
result.complete(start3());
print('Leaving f3');
return result.future;
}
void main() {
print('Starting main');
f1().then((_) {
f2().then((_) {
f3().then((_) {
});
});
});
print('Leaving main');
}
First of all you should clarify why you need this. I don't really get why you want them to be executed sequentially - you should give us some more information on the concept behind it.
Please give us some more information on what you want to accomplish! The following code does somehow what I think you want:
// I did some more cleanup to the code:
import 'dart:async';
Duration d1 = new Duration(seconds: 5);
Duration d2 = new Duration(seconds: 10);
Duration d3 = new Duration(seconds: 15);
Future<bool> f1() {
print('Entering f1');
Completer<bool> r = new Completer();
new Timer(d1, () {
r.complete(true);
});
print('Leaving f1');
return r.future;
}
Future<bool> f2() {
print('Entering f2');
Completer<bool> r = new Completer();
new Timer(d2, () {
r.complete(true);
});
print('Leaving f2');
return r.future;
}
Future<bool> f3() {
print('Entering f3');
Completer<bool> r = new Completer();
new Timer(d3, () {
r.complete(true);
});
print('Leaving f3');
return r.future;
}
void main() {
print('Starting main');
f1().then((_) {
f2().then((_) {
f3().then((_) {
});
});
});
print('Leaving main');
}
In start1, you return a future which is never completed. Basically, what you do is:
start1() {
var r = new Completer();
r.then.((_) { ... r.complete() ... });
return r.future;
}
Since the only complete of r requires r to be completed, that won't happen.
That means that the result of f1 will wait forever on the future returned by start1, and f1().then((_) ... will never get to the then callback. That's why nothing is happening.
You also have a busy-waiting loop while (!r1) {}. If you intend that to hang if r1 isn't true, then it works fine. Futures are not executed concurrently in Dart. You have to return to the event loop before any future callback is called, so if r1 is false when reaching the loop, it will stay false, because no other code will ever interrupt the loop.
If I understand what you want with start1, maybe try rewriting it to:
// Returns a future which completes when `r1` becomes true.
start1() {
var r = new Completer();
void check() {
if (r1) {
r.complete();
} else {
// We can't use scheduleMicrotask here, because an infinite microtask
// loop would prevent the timer from firing.
Timer.run(check); // Check again later.
}
}
check();
return r.future;
}
I have already try to understand the API doc, the articles about them, and this post: How do you create a Stream in Dart
I'm making a simple web app using WebSocket. Actually, it's working well, but I want add a feature (enjoy learn).
This is my class (can be optimized I guess)
library Ask;
import 'dart:html';
import 'dart:async';
import 'dart:convert';
class Ask {
final String addr;
String _protocol;
String _port;
WebSocket _ws;
bool openned;
Map<int, Completer> _completer_list = {};
int _counter = 0;
static final Map<String, Ask> _cache = <String, Ask>{};
factory Ask(String addr) {
if (_cache.containsKey(addr)) {
return _cache[addr];
} else {
final ask_server = new Ask._internal(addr);
_cache[addr] = ask_server;
return ask_server;
}
}
Ask._internal(this.addr);
Future<bool> open() {
if (openned)
return true;
_completer_list[0] = new Completer();
if (window.location.protocol == 'http:') {
_port = ':8080/ws';
_protocol = 'ws://';
} else {
_port = ':8443/ws';
_protocol = 'wss://';
}
_ws = new WebSocket(_protocol + addr + _port);
_ws.onOpen.listen((e) {
_get_data();
_get_close();
openned = true;
_completer_list[0].complete(true);
});
return _completer_list[0].future;
}
Future<String> send(Map data) {
bool check = false;
int id;
_completer_list.forEach((k, v) {
if (v.isCompleted) {
id = data['ws_id'] = k;
_completer_list[k] = new Completer();
_ws.send(JSON.encode(data));
check = true;
}
});
if (!check) {
_counter++;
id = data['ws_id'] = _counter;
_completer_list[id] = new Completer();
_ws.send(JSON.encode(data));
}
return _completer_list[id].future;
}
void _get_data() {
_ws.onMessage.listen((MessageEvent data) {
var response = JSON.decode(data.data);
_completer_list[response['ws_id']].complete(response);
});
}
void _get_close() {
_ws.onClose.listen((_) {
print('Server have been lost. Try to reconnect in 3 seconds.');
new Timer(new Duration(seconds: 3), () {
_ws = new WebSocket(_protocol + addr + _port);
_get_data();
_get_close();
_ws.onOpen.listen((e) => print('Server is alive again.'));
});
});
}
}
Example of use:
void showIndex() {
Element main = querySelector('main');
Ask connect = new Ask('127.0.0.1');
Map request = {};
request['index'] = true;
connect.open().then((_) {
connect.send(request).then((data) {
main.setInnerHtml(data['response']);
});
});
}
I would replace the then by a listen who will be canceled when the message will completed. By this way, I can add a progress bar, I think...
So my question, my send function can be a stream and keep my concept of one websocket for all ? (yes, if my function is used when a request is in progress, it's sent and if she's finish before the first, I recovered her properly. Thank you ws_id).
Thank you.
I think what you need is a StreamController
https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart-async.StreamController