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.
CodePudding user response:
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 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.)