See example below:
import 'dart:async';
Future<void> main(List<String> arguments) async {
try {
await someMethod();
} catch (e, st) {
print("error: ${e}");
print("st : ${st}");
}
}
Future<void> someMethod() async {
final completer = Completer.sync();
_scheduleCompletion(completer);
await completer.future;
}
Future<void> _scheduleCompletion(Completer completer) async {
Future.delayed(Duration(milliseconds: 1), () {
try {
[][0]; // simulating error
completer.complete();
} catch (e, st) {
completer.completeError("error occured", st);
}
});
}
I am receiving following output:
#0 List.[] (dart:core-patch/growable_array.dart:254:60)
#1 _scheduleCompletion.<anonymous closure> (file:///home/work/stuff/projects/dart-async-exceptions/bin/dart_async_exceptions.dart:26:9)
#2 new Future.delayed.<anonymous closure> (dart:async/future.dart:315:39)
#3 Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
#4 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:395:19)
#5 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:426:5)
#6 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
As you can see stacktrace does not allow you to see what steps led to error. What I expect is something like:
[anonymous closure, where error actually happens]
[someMethod]
[main]
while instead I see only last part.
I know there is package, called stack_trace, that allows you to "wrap your whole program in Chain.capture and you will be happy". Unfortunately my real-world case is not that simple and I can not simply "wrap my program" in "Chain.capture".
Another reason on not using "Chain.capture" thing is that in some cases it's onError handler gets even worse stacktrace vs. simple try/catch block .
Also I would like to understand why this is happening and what should I do to fix my program so part of valuable trace is not lost.
UPDATE (based on Irn's answer):
Let's re-write someMethod not to use completer, but rather call another async method:
Future<void> someMethod() async {
await someOtherMethod();
}
Future<void> someOtherMethod() async {
throw "another error";
}
In that case I will have following trace:
#0 someOtherMethod (file:///home/work/stuff/projects/dart-async-exceptions/bin/dart_async_exceptions_original.dart:24:3)
<asynchronous suspension>
#1 someMethod (file:///home/work/stuff/projects/dart-async-exceptions/bin/dart_async_exceptions_original.dart:19:3)
<asynchronous suspension>
#2 main (file:///home/work/stuff/projects/dart-async-exceptions/bin/dart_async_exceptions_original.dart:5:5)
<asynchronous suspension>
As you see it contains all invocations main -> someMethod -> someOtherMethod, that allows you to narrow down the issue. As I understand in that case we should also lose everything as we are scheduling calls on event loop. But we see everything works as expected. Why is that?
About stack_trace package: I would love to use it, but I made small experiment and looks like sometimes it provides worse trace then the one from try/catch. But that is topic for another question ;)
The short answer is that you can't trace a stack which doesn't exist.
Dart's asynchronous code is event loop based.
When you do await someFuture, your function really sets up a callback on that future, using Future.then, and then it returns. That unwinds the stack leading up to setting that callback, and eventually the stack all the way back to the event loop.
(The return only happens when you evaluate the await, the someFuture expression is invoked immediately, and if that manages to throw before doing something asynchronous, then you will get the stack trace containing the stack prior to the await returning).
At a later time, the event loop triggers and calls the callback, and the asynchronous computation continues from there.
The original stack no longer exists at that point, which is why you see a stack trace leading back to an event loop event (here, a timer, which the VM implements by sending port events at specific times). The stack you see is the only stack which exists at the point you ask for it.
You can use package:stack_trace to capture a stack trace at the point where you schedule a callback, and then the stack trace package will remember that stack trace until the callback is run, and attempt to combine it with the stack traces reported (or captured) during the callback.
Since you make lots of callbacks, but don't usually need to see that many stack traces, it has a significant performance impact to capture stacks at every callback.
Related
So I've read the docs and this example in particular:
runZoned(() {
HttpServer.bind('0.0.0.0', port).then((server) {
server.listen(staticFiles.serveRequest);
});
},
onError: (e, stackTrace) => print('Oh noes! $e $stackTrace'));
I understand the whole zone-local-values thing, and the sort of AOP option of tracing code through a zone - enter/exit.
But other than that, is the code block above any different than:
try {
var server = await HttpServer.bind('0.0.0.0', port);
server.listen(staticFiles.serveRequest);
} catch (e, stackTrace) {
print('Oh noes! $e ${e.stackTrace}');
}
Thanks :)
The runZoned code introduces a new uncaught error handler.
Some asynchronous errors do not happen in a context where they can be thrown to user code. For example, if you create a future, but never await it or listen to it in any other way, and that future then completes with an error, that error is considered "uncaught" or "unhandled". Futures work using callbacks, and there has been no callback provided that it can call.
In that case, the current zone's uncaught error handler is invoked with the error.
Your first example introduces such a handler, which means that if the body of the runZoned call has any uncaught async errors, you'll be told about them.
That includes the future returned by calling then, but also any other asynchronous errors that may happen (if any).
When you await a future, and that future completes with an error, that error is (re)thrown, an can be caught. The try/catch of the second example will catch that error (and any thrown by calling listen, which shouldn't be any as long as server isn't null, which it shouldn't be).
So, the difference is only really in whether uncaught asynchronous errors are handled. (And how any errors in the catch code is propagated, because an error happening during an uncaught-error zone handler will again be uncaught.)
I'm having trouble successfully using shareReplay from RxDart (0.24.1) in my application. I have a Stream and I would like to 'cache' the latest emitted value, so that any 'late' listeners get it immediately (the stream can be idle for longer periods). I'm failing to do this, so I started experimenting outside of my app and must admit I don't fully understand what is happening.
Here is some code:
import 'package:rxdart/rxdart.dart';
void main() async {
final s = Stream.fromIterable([1, 2, 3]).shareReplay(maxSize: 10);
print('First:');
await for (final i in s) {
print(i);
}
print('Second:');
await for (final i in s) {
print(i);
}
}
I would expect the code to print
First
1
2
3
Second
1
2
3
but it never gets to the second await for, it doesn't even print the string Second:; however, the program finishes successfully, so the first await for does finish (as the original Stream is finite). What is going on?
In reality, the Stream that I have is potentially endless with long periods of time between events), and I'm only interested in the last event, so here is some code simulating this:
import 'package:rxdart/rxdart.dart';
void main() async {
final s = Stream.periodic(Duration(seconds: 1), (e) => e).shareReplay(maxSize: 1);
// print(s.isBroadcast);
await Future.delayed(Duration(seconds: 3));
print('First');
await for (final i in s.take(1)) {
print(i);
}
print('Second');
await for (final i in s.take(1)) {
print(i);
}
}
To make sure the Stream can finish (and so await for can finish) I use take(1). I expected the output to be:
First
2
Second
2
(Maybe the twos would be threes?)
What I expected to happen is this:
The periodic stream starts emitting every second (it is broadcast/hot).
The application waits for 3 seconds due to Future.delayed.
The first late listener comes and gets the latest value, 2 (or maybe 3?) and finishes due to take(1).
The second late listener comes and gets the latest value as well because the stream didn't emit anything else in the meantime (even if it does, the value would be greater by 1, which is Ok) and finishes due to take(1).
The app finishes.
However, the output is:
First
0
Second
Unhandled exception:
type '_TakeStream<int>' is not a subtype of type 'Future<bool>'
#0 _StreamIterator._onData (dart:async/stream_impl.dart:1067:19)
#1 _RootZone.runUnaryGuarded (dart:async/zone.dart:1384:10)
#2 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:357:11)
#3 _BufferingStreamSubscription._add (dart:async/stream_impl.dart:285:7)
#4 _ForwardingStreamSubscription._add (dart:async/stream_pipe.dart:127:11)
#5 _TakeStream._handleData (dart:async/stream_pipe.dart:318:12)
#6 _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:157:13)
#7 _RootZone.runUnaryGuarded (dart:async/zone.dart:1384:10)
#8 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:357:11)
#9 _BufferingStreamSubscription._add (dart:async/stream_impl.dart:285:7)
#10 _SyncBroadcastStreamController._sendData (dart:async/broadcast_stream_controller.dart:385:25)
#11 _BroadcastStreamController.add (dart:async/broadcast_stream_controller.dart:250:5)
#12 _StartWithStreamSink._safeAddFirstEvent (package:rxdart/src/transformers/start_with.dart:56:12)
#13 _StartWithStreamSink.onListen (package:rxdart/src/transformers/start_with.dart:37:11)
#14 forwardStream.<anonymous closure>.<anonymous closure> (package:rxdart/src/utils/forwarding_stream.dart:31:37)
#15 forwardStream.runCatching (package:rxdart/src/utils/forwarding_stream.dart:24:12)
#16 forwardStream.<anonymous closure> (package:rxdart/src/utils/forwarding_stream.dart:31:16)
#17 _runGuarded (dart:async/stream_controller.dart:847:24)
#18 _BroadcastStreamController._subscribe (dart:async/broadcast_stream_controller.dart:213:7)
#19 _ControllerStream._createSubscription (dart:async/stream_controller.dart:860:19)
#20 _StreamImpl.listen (dart:async/stream_impl.dart:493:9)
#21 DeferStream.listen (package:rxdart/src/streams/defer.dart:37:18)
#22 StreamView.listen (dart:async/stream.dart:1871:20)
#23 new _ForwardingStreamSubscription (dart:async/stream_pipe.dart:118:10)
#24 new _StateStreamSubscription (dart:async/stream_pipe.dart:341:9)
#25 _TakeStream._createSubscription (dart:async/stream_pipe.dart:310:16)
#26 _ForwardingStream.listen (dart:async/stream_pipe.dart:83:12)
#27 _StreamIterator._initializeOrDone (dart:async/stream_impl.dart:1041:30)
#28 _StreamIterator.moveNext (dart:async/stream_impl.dart:1028:12)
#29 main (package:shopping_list/main.dart)
<asynchronous suspension>
#30 _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#31 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
The first await for with take(1) to actually make it finish works as I would expect it to, with the exception that it gets the the initial value, which means the stream didn't stat emitting anything before the listener came (is not 'hot'). The second fails with an exception. What am I missing?
EDIT: my understanding of shareReplay may have been wrong all along, I though it is hot because of https://pub.dev/documentation/rxdart/latest/rx/ReplaySubject-class.html says it is hot.
I changed the code a bit:
import 'package:rxdart/rxdart.dart';
void main() async {
final s = BehaviorSubject();
s.addStream(Stream.periodic(Duration(seconds: 1), (e) => e));
// print(s.isBroadcast);
await Future.delayed(Duration(seconds: 3));
print('First');
await for (final i in s.take(1)) {
print(i);
}
print('Second');
await for (final i in s.take(1)) {
print(i);
}
}
and now the output is:
First
2
Second
2
BUT the program never finishes...
Without take(1) the output never gets to the second listener, so it does have some effect. I don't get it.
EDIT 2: I think I might know why the first example with shareReplay doesn't work, here is an excerpt from the docs:
It will automatically begin emitting items when first listened to, and shut down when no listeners remain.
In my case, the first listener/await for subscribes, and the stream starts, and when the loop is done the last (and only) listener is finished, so the stream returned from shareReplay shuts down. Still doesn't explain why the line print('Second'); doesn't execute, though.
The reason your final program doesn't terminate is that your broadcast stream is still emitting events every second.
Nobody's listening, but it's a broadcast stream so it doesn't care. It has a timer ticking every second, and that timer keeps the program alive, even if nobody is ever going to listen again.
You can keep your program alive by doing Timer.periodic(Duration(seconds: 1), (_) {}); as well. As long as the timer is live, something might happen.
The crash is interesting, though. It suggests a problem with the internal logic of the StreamIterator class. I'll look into that.
Can't say anything guaranteed useful about shareReplay, I'm not familiar with RxDart.
My guess is that the first program gets into a problem because the replayShared stream doesn't terminate properly - that is, doesn't send a done event when it's done. That would explain the program terminating early. The first await for is waiting for the next event, so it doesn't end and go to the next loop, but there are no live timers or scheduled events of any kind left, so the isolate just shuts down.
A quick read of BeheaviorSubject suggests that it works like a stream controller, so you probably need to close it after the addStream has completed. Maybe:
s.addStream(...).whenComplete(s.close);
I've always considered async/await more elegant/sexy over the Futures API, but now I'm faced with a situation where the Future API implementation is very short and concise and the async/await alternative seems verbose and ugly.
I marked my two question #1 and #2 in the comments:
class ItemsRepository
{
Future<dynamic> item_int2string;
ItemsRepository() {
// #1
item_int2string =
rootBundle.loadString('assets/data/item_int2string.json').then(jsonDecode);
}
Future<String> getItem(String id) async {
// #2
return await item_int2string[id];
}
}
#1: How do I use async/await here instead of Future.then()? What's the most elegant solution?
#2: Is this efficient if the method is called a lot? How much overhead does await add? Should I make the resolved future an instance variable, aka
completedFuture ??= await item_int2string;
return completedFuture[id];
1: How do I use async/await here instead of Future.then()? What's the most elegant solution?
async methods are contagious. That means your ItemsRepository method has to be async in order to use await inside. This also means you have to call it asynchronously from other places. See example:
Future<dynamic> ItemsRepository() async {
// #1
myString = await rootBundle.loadString('assets/data/item_int2string.json');
// do something with my string here, which is not in a Future anymore...
}
Note that using .then is absolutely the same as await in a async function. It is just syntactic sugar. Note that you would use .then differently than in your example though:
ItemsRepository() {
// #1
rootBundle.loadString('assets/data/item_int2string.json').then((String myString) {
// do something with myString here, which is not in a Future anymore...
});
}
And for #2 don't worry about a performance impact of async code. The code will be executed at the same speed as synchronous code, just later whenever the callback happens. The only reason async exists is for having an easy way of allowing code to continue running while the system waits for the return of the asynchronously called portion. For example not block the UI while waiting for the disk to load a file.
I recommend you read the basic docs about async in Dart.
then and await are different. await will stop the program there until the Future task is finished. However then will not block the program. The block within then will be executed when the Future task is finished afterwards.
If you want your program to wait for the Future task, then use await. If you want your program to continue running and the Future task do it things "in the background", then use then.
I have two functions
callee() async {
// do something that takes some time
}
caller () async {
await callee();
}
In this scenario, caller() waits till callee() finishes. I don't want that. I want caller() to complete right after invoking callee(). callee() can complete whenever in the future, I don't care. I just want to start it just like a thread and then forget about it.
Is this possible?
When you call the callee function, it returns a Future. The await then waits for that future to complete. If you don't await the future, it will eventually complete anyway, but your caller function won't be blocked on waiting for that. So, you can just do:
caller() {
callee(); // Ignore returned Future (at your own peril).
}
If you do that, you should be aware of what happens if callee fails with an error. That would make the returned future complete with that error, and if you don't listen on the future, that error is considered "uncaught". Uncaught errors are handled by the current Zone and the default behavior is to act like a top-level uncaught error which may kill your isolate.
So, remember to handle the error.
If callee can't fail, great, you're done (unless it fails anyway, then you'll have fun debugging that).
Actually, because of the risk of just forgetting to await a future, the highly reocmmended unawaited_futures lint requires that you don't just ignore a returned future, and instead wants you to do unawaited(callee()); to signal that it's deliberate. (The unawaited function can be imported from package:meta and will be available from the dart:async library in SDK version 2.14).
The unawaited function doesn't handle errors though, so if you can have errors, you should do something more.
You can handle the error locally:
caller() {
callee().catchError((e, s) {
logErrorSomehow(e, s);
});
}
(Since null safety, this code only works if the callee() future has a nullable value type. From Dart 2.14, you'll be able to use callee().ignore() instead, until then you can do callee().then((_) => null, onError: (e, s) => logErrorSomehow(e, s)); instead.)
or you can install an error handling zone and run your code in that:
runZoned(() {
myProgram();
}, onError: logErrorSomehow);
See the runZoned function and it's onError parameter.
Sure, just omit await. This way callee() is called immediately and when an async operation is called the call will be scheduled in the event queue for later execution and caller() is continued immediately afterwards.
This isn't like a thread though. As mentioned processing is enqueued to the event queue which means it won't be executed until the current task and all previously enqueued tasks are completed.
If you want real parallel execution you need to utilize isolates.
See also
https://www.dartlang.org/articles/event-loop/
https://api.dartlang.org/stable/1.16.1/dart-isolate/dart-isolate-library.html
https://pub.dartlang.org/packages/isolate
I have an application that makes heavy use of audio. I use AVAudioPlayers to simplify the task.
I've noticed crashes from time to time when using the players, usually when I call [player play]. The stack trace looks something like this:
EXC_BAD_ACCESS
#0 0x32d531e6 in memmove$VARIANT$CortexA8 ()
#1 0x351056dc in Cached_DataSource::ReadFromHeaderCache ()
#2 0x351052a4 in Cached_DataSource::ReadBytes ()
#3 0x350ba47c in AudioFileObject::ReadBytes ()
#4 0x35104562 in AudioFileObject::ReadPacketDataVBR ()
#5 0x35102c70 in AudioFileObject::ReadPacketData ()
#6 0x351020b2 in AudioFileReadPacketData ()
#7 0x36bad1b8 in AudioPlayerAQOutputCallbackCore ()
#8 0x36bad7a4 in prepareToPlayQueue ()
#9 0x36bad954 in playQueue ()
#10 0x36bac27e in -[AVAudioPlayer play] ()
I have breakpoints set up in every dealloc call, so I know that I'm not inadvertently dealloc-ing a player or delegate or similar.
The players are sometimes stopped before they play their full sound, so I always make a [player setCurrentTime:0] call before playing. I'm not sure if this could cause some negative side effects.
Clearly the audio queue is trying to access invalid memory, but from the looks of things that memory lives way down in the file cache code, so I can't see how anything I'm doing would affect it. All of the files I'm playing are included as resources in the main bundle.
I created a repro that reliably reproduces this in the 4.1 simulator and on my 5.0.1 device. It can take a little while and hundreds of calls to setCurrentTime, play, and stop before it crashes.
I need to get to the bottom of this. I can start porting all of my code to use audio queues or something else if required, but I want to make sure that there isn't something I'm missing or that I could do differently to make this more stable.