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();
});
Related
I am failing to understand, why the error thrown from addItem method in below code is not caught in the try-catch block
void main() async {
var executor = Executor();
var stream = Stream.fromIterable([0, 1, 2, 3, 4, 5, 6, 7]);
try {
await for (var _ in stream) {
executor.submit(() => demoMethod());
}
await executor.execute();
} catch (e) {
print(e);
}
}
Future<void> demoMethod() async {
var list = [1, 2, 3, 1, 4, 5];
var executor = Executor();
var test = Test();
for (var element in list) {
executor.submit(() => test.addItem(element));
}
await executor.execute();
test.list.forEach(print);
}
class Test {
var list = <int>[];
Future<void> addItem(int i) async {
if (list.contains(i)) {
throw Exception('Item exists');
}
list.add(i);
}
}
class Executor {
final List<Future<void>> _futures = [];
bool _disposed = false;
void submit(Future<void> Function() computation) {
if (!_disposed) {
_futures.add(computation());
} else {
throw Exception('Executor is already disposed');
}
}
Future<void> execute() async {
await Future.wait(_futures, eagerError: true);
_disposed = true;
}
}
but below code is able to catch the error properly
void main() async {
var executor = Executor();
try {
for (var i = 0; i < 10; i++) {
executor.submit(() => demoMethod());
}
await executor.execute();
} catch (e) {
print(e);
}
}
I am guessing it has something to do with the stream processing.
It's the stream.
In your other examples, you synchronously run through a loop a and call Executor.submit with all the computations, then immediately call executor.execute().
There is no asychronous gap between calling the function which returns a future, and Future.wait starting to wait for that future.
In the stream code, each stream events starts an asynchronous computation by calling Executor.submit. That creates a future, stores it in a list, and goes back to waiting for the stream.
If that future completes, with an error, before the stream ends and Future.wait gets called, then there is no error handler attached to the future yet. The error is then considered unhandled, and is reported to the current Zone's uncaught error handler. Here that's the root zone, which means it's a global uncaught error, which may crash your entire program.
You need to make sure the future doesn't consider its error unhandled.
The easiest way to do that is to change submit to:
void submit(Future<void> Function() computation) {
if (!_disposed) {
_futures.add(computation()..ignore());
} else {
throw StateError('Executor is already disposed');
}
}
The ..ignore() tells the future that it's OK to not have an error handler.
You know, because the code will later come back and call executor.execute, that any errors will still be reported, so it should be safe to just postpone them a little. That's what Future.ignore is for.
(Also changed Exception to StateError, because that's what you should use to report people using objects that have been disposed or otherwise decommissioned.)
When I try to run the below code, it completes in a little more than 4 seconds. I couldn't understand why it finishes in that time. I thought it would complete in 14 seconds(4sec in declaring order variable,10sec in for loop). Don't Future.delayed() stop all the progress in program?
Future<void> printOrderMessage() async {
print("Awaiting user order ...");
var order = await fetchUserOrder(); //I couldn't understand here.
print('Your order is: $order');
}
Future<String> fetchUserOrder() {
return Future.delayed(const Duration(seconds: 4), () => 'Large Latte');
}
void main() async {
countSeconds(4); //Başlama yeri
await printOrderMessage();
}
void countSeconds(int s) {
for (var i = 1; i <= s; i++) {
Future.delayed(Duration(seconds: i), () => print(i)); //Also here
}
}
Output:
Awaiting user order ...
1
2
3
4
Your order is: Large Latte
I have the following Dart code that doesn't behave as I expected:
final File file = File("result.csv");
Future send(String message) async {
try {
await file.writeAsString(message + '\n',
mode: FileMode.append, flush: true);
} catch (e) {
print("Error: $e");
}
return await file.length();
}
main() async {
final futures = <Future>[];
for (int i = 0; i < 100; i++) {
futures.add(send("$i"));
}
for (int i = 0; i < 100; i++) {
print(await futures[i]);
}
}
I expected the file to be written as soon as each call to await futures[i] in the second loop returned. However this does not seem to be happening.
The file should contain one line for each index from 0 to 99. But it contains a line with 99 followed by an empty line. And the print calls in the second loop always print the same file length, 3.
The event loop seems to be somehow merging the calls and only actually executing the last call, even though I still get 100 different futures that I await in the second loop.
Why is this happening and how can I allow the futures to run without awaiting them immediately (I really need to await only later, when all of the calls to send have been made)?
With the loop:
for (int i = 0; i < 100; i++) {
futures.add(send("$i"));
}
multiple send are immediately invoked and each one concurrently open and write a string in the file: you have a race condition and at the ends it happens that only one message it is written to the file.
Use a list of functions that return a future
With a closure it is possible to implement a sequenzialized version for file access that avoid the race condition.
Instead of creating a list of futures, create a list of functions that returns a future:
The shared file resource is accessed sequentially if you call and await such functions in a loop:
import 'dart:io';
final File file = File("result.csv");
typedef Future SendFunction();
Future send(String message) async {
try {
await file.writeAsString(message + '\n',
mode: FileMode.append, flush: true);
} catch (e) {
print("Error: $e");
}
var l = await file.length();
return l;
}
main() async {
final futures = List<SendFunction>();
//final futures = <Future>[];
for (int i = 0; i < 100; i++) {
//futures.add(send("$i"));
futures.add(() => send("$i"));
}
for (int i = 0; i < 100; i++) {
print(await futures[i]());
//print(await futures[i]);
}
}
synchronized package
synchronized offers lock mechanism to prevent concurrent access to asynchronous code.
In this case it may be used to avoid concurrent access to result.csv file:
import 'dart:io';
import 'package:synchronized/synchronized.dart';
final File file = File("result.csv");
final lock = new Lock();
Future send(String message) async {
try {
await file.writeAsString(message + '\n',
mode: FileMode.append, flush: true);
} catch (e) {
print("Error: $e");
}
return await file.length();
}
main() async {
final futures = <Future>[];
for (int i = 0; i < 100; i++) {
futures.add(lock.synchronized(() => send("$i")));
}
for (int i = 0; i < 100; i++) {
print(await futures[i]);
}
}
This is related to is there any way to cancel a dart Future?
In my case, there are no HTTP, just expensive calculations. I have a table/list which I scroll through. As the elements become visible, I generate futures to show the calculation results. But if I (the end user) scroll quickly, some results will have "scrolled out of view" and will no longer required. This could be a large number, and would seriously delay the return of futures (results) that are to be usefully :-) displayed in currently visible elements. Can something be done about that? cheers, Steve
You could just set a flag which indicates to the delayed code (run from futures) that the result isn't needed anymore.
When the delayed code is called it just returns.
library cancel_future;
import 'dart:async' show Future, Timer;
import 'dart:math' show Random;
typedef void TaskFunction(Task task);
// Container for a task
class Task {
// an assigned task id
final id;
// data to process
int data;
// Indicate to the task function, that it should stop processing
bool isCanceled = false;
// The task function must set this flat to true when all work is done.
bool isFinished = false;
// The task function which processed the data and sets the result.
TaskFunction fn;
// The result set by the task function when it finished processing.
int result;
Task(this.id, this.data, this.fn);
// Start processing the task.
void execute() => fn(this);
}
final rnd = new Random();
void main(List<String> args) {
// create tasks
final tasks = new List<Task>.from(generate());
// start all tasks
tasks.forEach((t) => t.execute());
// after random delay cancel all unfinished tasks
new Future.delayed(new Duration(seconds: rnd.nextInt(10)), () {
tasks.forEach((t) {
if (!t.isFinished) {
t.isCanceled = true;
}
});
}).then((_) {
// check results
int done = 0;
int canceled = 0;
tasks.forEach((t) {
print(
'Task id: ${t.id}; isCanceled: ${t.isCanceled}; isFinished: ${t.isFinished}; data: ${t.data}; result: ${t.result}');
if (t.isFinished) {
done++;
}
if (t.isCanceled) {
canceled++;
}
});
print('Canceled: $canceled.');
print('Done: $done.');
});
}
// geneator for job 100 jobs
Iterable<Task> generate() sync* {
int i = 0;
while (i++ < 100) {
yield new Task(i, rnd.nextInt(100), calc);
}
}
// job function
void calc(Task t) {
// do a bit of work every 100ms to simulate longer processing
new Timer.periodic(new Duration(milliseconds: 100), (timer) {
var result = 0;
// check if jost was canceled and stop processing in case it was.
if (t.isCanceled) {
timer.cancel();
return;
}
// while not finished do a chunk of work
if (result < t.data) {
result++;
} else {
// finished - clean up and store result
t.isFinished = true;
t.result = result;
timer.cancel();
}
});
}
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;
}
}