I have code similar to:
timer = new Timer(new Duration(milliseconds: 1000), () => (throw new TimeoutException('Callback not invoked!')));
while (timer.isActive){
await new Future.delayed(const Duration(milliseconds: 1), () => "1");
}
print('this should not be reached if the exception is raised');
elsewhere I have an async callback which calls:
timer.cancel();
In the case where the callback is invoked it works fine because the callback cancels the timer.
However, I'm not really sure how to actually catch the TimeoutException in this case if it is not canceled, because it seems the exception is raised in a different scope than my main function. This means program execution continues even though
Is there a way to do some sort of try/catch or somehow handle the above timeout exception? Or a better way to do what I am trying to do?
Using dart 1.19.1.
You get different behavior depending on whether the timeout is 500ms or 1500ms:
final timer = new Future.delayed(const Duration(milliseconds: 1000),
() => (throw new Exception('Callback not invoked!')))
.timeout(const Duration(milliseconds: 500));
try {
await timer;
} on TimeoutException catch(e) {
print('this should not be reached if the exception is raised');
} on Exception catch(e) {
print('exception: $e');
}
DartPad
Related
I have Flutter mobile app that is using Riverpod with hooks.
I have the following function that I would like to be called when the widget is disposed:
useEffect(
() {
final firestoreRepo =
ref.read(firebaseFirestoreRepositoryProvider);
return () async {
try {
// I get exception at this line.
// I need this future to be called when the
// widget is disposed.
// Calling this future earlier is not userful
// for my business logic.
final relationship =
await ref.read(relationshipWithProvider(pid).future);
if (relationship?.unseen ?? false) {
await firestoreRepo?.updateRelatinoship(pid: pid);
}
} catch (e, st) {
// print error
}
};
},
[],
);
I keep getting this error at the line shown in the comment above.
I/flutter ( 5967): Looking up a deactivated widget's ancestor is unsafe.
I/flutter ( 5967): At this point the state of the widget's element tree is no longer stable.
How can I sold this problem
We can initially get our relationship and then await and use it:
useEffect(
() {
final firestoreRepo = ref.read(firebaseFirestoreRepositoryProvider);
final relationship = ref.read(relationshipWithProvider(pid).future);
return () async {
try {
if (await relationship?.unseen ?? false) {
await firestoreRepo?.updateRelatinoship(pid: pid);
}
} catch (e, st) {
// print error
}
};
},
[],
);
As far as I can tell, this won't contradict the logic of the business process, because one way or another, we'll have to make the relationshipWithProvider(pid) request early (when we initialize the widget) or late (when we delete the widget).
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.)
Whenever a user wants to play some audio, first I want to wait 5 seconds and every second perform an action. Whenever the user pushes play button, I call onPlay() method. My first attempt of it was this:
Timer? _timer; // I might need to cancel timer also when the user pauses playback by pause button or something
void onPlay() async {
int _secondsLeft = 5;
_timer = await Timer.periodic(const Duration(seconds: 1), (_) {
if (_secondsLeft == 0) _timer?.cancel();
_textToSpeech.speak(_secondsLeft.toString());
--_secondsLeft;
});
audio.play(_curSong);
}
This attempt was not working correctly as audio.play() run immediately after Timer was initialized, not finished. I found How to make Timer.periodic cancel itself when a condition is reached? and used one of the answers to improve my code like this:
StreamSubscription? _timer;
void onPlay() async {
int _secondsLeft = 5;
_timer = await Stream.periodic(const Duration(seconds: 1))
.takeWhile((_) => _secondsLeft > 0)
.forEach((_) {
_textToSpeech.speak(_secondsLeft.toString());
--_secondsLeft;
});
audio.play(_curSong);
}
This worked better, but if the user switches to a different song or pauses playback during the intial phase, _timer?.cancel() will only cancel the stream, so everything after it (audio.play(_curSong)) is still being run. But I would like to cancel not only the stream, but also skip the audio.play() function. So I would like to run audio.play() only after the Timer/Stream finishes properly without cancellation.
Is there any option to handle Stream.periodic or Timer.periodic cancel action ("onCancel")? Or is there a better way to handle this specific task?
Almost there. Your await on .forEach actually returns null. So there is no StreamSubscription as far as I can see.
The callback that might be the simple solution for your problem is the optional onDone in the listen method. This won't be called when Stream is cancelled.
import 'dart:async';
void main() async {
onPlay();
await Future.delayed(Duration(seconds: 2));
// Uncomment me to test it
// _timer?.cancel();
}
StreamSubscription? _timer;
void onPlay() {
int _secondsLeft = 5;
_timer = Stream.periodic(const Duration(seconds: 1))
.takeWhile((_) => _secondsLeft > 0)
.listen((event) {
print(_secondsLeft.toString());
--_secondsLeft;
}, onDone: () {
print("It's show time!");
});
}
You are very close, you just need to only play the song when the timer has counted down, at the same time you cancel the timer.
So:
Timer? _timer;
void onPlay() async {
int _secondsLeft = 5;
_timer = await Timer.periodic(const Duration(seconds: 1), (_) {
if (_secondsLeft == 0) {
_timer?.cancel();
audio.play(_curSong);
} else {
_textToSpeech.speak(_secondsLeft.toString());
--_secondsLeft;
}
});
}
(Notice that this doesn't say zero, where your original did. Instead it plays the music. If you want it to say zero, change the == 0 to < 0.)
With this way of writing it, canceling the timer stops you from playing the song.
When defining a Future as follows:
Future<HttpRequest> httpRequest = HttpRequest.request(url,
method: method, requestHeaders: requestHeaders);
I want to handle a timeout after 5 secondes. I'm writing my code like this :
httpRequest.timeout(const Duration (seconds:5),onTimeout : _onTimeout());
Where my timeout function is :
_onTimeout() => print("Time Out occurs");
According to the Future timeout() method documentation , If onTimeout is omitted, a timeout will cause the returned future to complete with a TimeoutException. But With my code , my method _onTimeout() is properly called (but immediately, not after 5 seconds) and I always get a
TimeException after 5 seconds... (TimeoutException after 0:00:05.000000: Future not completed )
Am I missing something ?
Change this line
httpRequest.timeout(const Duration (seconds:5),onTimeout : _onTimeout());
to
httpRequest.timeout(const Duration (seconds:5),onTimeout : () => _onTimeout());
or just pass a reference to the function (without the ())
httpRequest.timeout(const Duration (seconds:5),onTimeout : _onTimeout);
This way the closure that calls _onTimeout() will be passed to timeout().
In the former code the result of the _onTimeout() call will be passed to timeout()
Future.await[_doSome].then((data){
print(data);
}).timeout(Duration(seconds: 10));
Using async/await style. You can add .timeout to any Future you are awaiting.
final result = await InternetAddress
.lookup('example.com')
.timeout(
Duration(seconds: 10),
onTimeout: () => throw TimeoutException('Can\'t connect in 10 seconds.'),
);
In order to stop any Future by timeout one can use timeout(). There are two examples:
Throw exception after timeout
final someHardTaskFuture = Future.delayed(const Duration(hours: 1), () => 42);
final newFutureWithTimeoutAndException = someHardTaskFuture.timeout(const Duration(seconds: 3));
Returns default value (11) on timeout
final someHardTaskFuture = Future.delayed(const Duration(hours: 1), () => 42);
final newFutureWithTimeoutAndDefaultValue = someHardTaskFuture
.timeout(const Duration(seconds: 3), onTimeout: () => 11);
print(await newFutureWithTimeoutAndDefaultValue);
I launch a request to a server with a future "requestServer".
I would like to poll a system for a specific value (passed from false to true, when request is done) and return when finished.
Code could be like that, but "while" synchronous and "checkOperation" is asynchronous?
return requestServer().then((operation) {
var done = false;
while (done)
return checkOperation(operation).then((result) {
done = (result == true);
});
sleep(10);
}
});
Any ideas ?
I guess this is not exactly what you want but as far as I know there is no way to block execution so you have to use callbacks.
void main(List<String> args) {
// polling
new Timer.periodic(new Duration(microseconds: 100), (t) {
if(isDone) {
t.cancel();
someCallback();
}
});
// set isDone to true sometimes in the future
new Future.delayed(new Duration(seconds: 10), () => isDone = true);
}
bool isDone = false;
void someCallback() {
print('isDone: $isDone');
// continue processing
}
You can of course pass the callback as parameter instead of hardcode it, because functions are first class members in Dart.
Polling doesn't work very well for async. It is better to wait for a signal from the thing that must complete.
Günter Zöchbauer's answer shows you how to poll anyway, by sampling with a timer.
As an alternative, it would be better to not have a boolean done, but instead complete another future when you are ready. This is busy-polling, which polls again as soon as a result comes back, which may be more intensive than you need. Using timer based polling can be more efficient if you don't need the result as soon as possible.
return requestServer().then((operation) {
var completer = new Completer();
void poll(result) {
if (!result) {
operation.then(poll, onError: completer.completeError);
} else {
completer.complete();
}
}
poll(false);
return completer.future;
});
(Code not really tested, since I don't have your requestServer).
When you want build functions that return Futures, it is sometimes useful to use Completers. Think that requestServer() is living in the Future too, so you will have threat the result as a Future.
return requestServer().then((operation) {
// This is necessary then you want to control async
// funcions.
Completer completer = new Completer();
//
new Timer.periodic(const Duration(seconds: 10), (_) {
checkOperation(operation).then((result) {
// Only when the result is true, you pass the signal
// that the operation has finished.
// You can alse use `completer.complete(result)` if you want
// to pass data inside of the future.
if (result == true) completer.complete();
});
});
// You return the future straight away.
// It will be returned by requestServer();
return completer.future;
});
I use a function like this in a TestUtil library:
static Future<bool> waitUntilTrue(bool Function() callback,
{Duration timeout: const Duration(seconds: 2),
Duration pollInterval: const Duration(milliseconds: 50)}) {
var completer = new Completer<bool>();
var started = DateTime.now();
poll() {
var now = DateTime.now();
if (now.difference(started) >= timeout) {
completer.completeError(Exception('timed out in waitUntilTrue'));
return;
}
if (callback()) {
completer.complete(true);
} else {
new Timer(Duration(milliseconds: 100), () {
poll();
});
}
}
poll();
return completer.future;
}
And then in my test code I'll do something like:
await TestUtil.waitUntilTrue(() => someObj.isDone);
Edit:
Note that if you're using this in a testWidgets test, you have to do a little extra, since it relies on real async work happening:
await tester.runAsync<bool>(
() => TestUtil.waitUntilTrue(() => myObj.isLoaded),
additionalTime: Duration(seconds: 5));