How to handle "onCancel" of Timer.periodic or Stream.periodic - dart

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.

Related

Understanding Future.delayed()

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

How to set timeout function for RawDatagramSocket in Dart

I have a Dart application which receives udp datagram every 10ms. I want to know when data stream is stopped. I have a RawDatagramSocket as _udpSocket I can listen datagram by using this function:
RawDatagramSocket? _udpSocket;
Future<void> bindSocket() async {
_udpSocket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, port);
setTimeout();
}
Future<void> listen() async {
_udpSocket?.listen((event) async {
Datagram? d = _udpSocket?.receive();
if (d == null) {
return;
}
//handle received data
});
}
And also I have a function to handle timeout:
void setTimeout() {
//if there is no data receiving in a couple of cycle, time out will be triggered.
//1 cycle == 10ms, I want timeout to be triggered after 10 cycles. (100ms)
_udpSocket?.timeout(timeoutDuration, onTimeout: (sink) {
//Handle time out
});
}
I am able to receive and process incoming data, but timeout function is not working.
Is there anything wrong with my code, or is there any other method to do what I want.
I figured it out,I updated the listen function. Here is the update for those who would need it:
final Duration timeoutDuration = const Duration(milliseconds: 100);
#override
Future<void> listen() async {
_udpSocket?.timeout(timeoutDuration, onTimeout: ((sink) {
//do your work when data stream closed
})).listen((event) async {
Datagram? d = _udpSocket?.receive();
if (d == null) {
return;
}
//handle received data
});
}
I hope it will be useful.

can I know when a StreamSubscription is cancelled?

is there anything like onCancel for a StreamSubscription?
example:
var subscription = someStream.listen((item) => null);
subscription.cancel(); // does this trigger any event?
I ended up creating a _StreamSubscriptionDelegate that delegates all methods and so I can put some logic when the subscription is cancelled, however, maybe there is an easier solution to it.
If the stream comes from a StreamController, then the controller is notified of the cancel. The listener is expected to keep track of their own subscription, so if one part of the client code needs to know that another part has cancelled the stream, then wrapping the subscription in something which records that you cancelled it, is a perfectly good approach.
Another approach could be to wrap the stream before listening to it:
Stream<T> onCancel<T>(Stream<T> source, void onCancel()) async* {
bool isCancelled = true;
try {
await for (var event in source) {
yield event; // exits if cancelled.
}
isCancelled = false;
} finally {
if (isCancelled) onCancel();
}
}
or
Stream<T> onCancel<T>(Stream<T> source, void onCancel()) {
var sink = StreamController<T>();
sink.onListen = () {
var subscription = source.listen(sink.add, onError: sink.onError, onDone: sink.close);
sink
..onPause = subscription.pause
..onResume = subscription.resume
..onCancel = () {
subscription.cancel();
onCancel();
};
};
return sink.stream;
}

How to delete a Dart future when it's no longer needed

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();
}
});
}

Execute Futures until a parameter becomes true

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));

Resources