Dartで数秒wait(sleep)したいというのはよくあるニーズだと思いますが、方法を検索すると、大抵は Future.delayed(Duration(..))
のサンプルがHITします。
個人的にはこの方法で困った事が無かったので、無操作/無通信タイムアウト処理を実装するにもこの周辺を調べれば良いと考えましたが、タイマキャンセルの方法が見つかりません。
調査の結果、なんだTimerクラスを使えばよかったんじゃん、という記事です。
なぜタイマキャンセル出来ないのか
https://api.dartlang.org/stable/2.1.0/dart-async/Future/Future.delayed.html にもありますが、Future.delayedの中でTimerクラスが使用されています。
が、Timerのインスタンスへの参照が保持されていません。
factory Future.delayed(Duration duration, [FutureOr<T> computation()]) {
_Future<T> result = new _Future<T>();
new Timer(duration, () {
try {
result._complete(computation?.call());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
Timerクラス自体はcancel()
メソッドを持っているのですが、誰もcancel()
メソッドを呼べないのです。
(理由はあるかもしれませんが深追いはしません)
タイマをキャンセルする
しかしFuture.delayedの実装を見たおかげで、Timerクラスを直接呼べば良いということが分かりました。
/// イメージ(60秒間、無通信なら終了する)
class WatchDog {
Timer _timer;
void ondata(String s) {
_timer?.cancel(); // 一度タイマをキャンセルし、
_timer = Timer(Duration(seconds: 60), _nodata); // もう一度60秒で張り直す。
something(s);
}
void _nodata() => exit(0);
}
TimerクラスはJavaScriptで言うsetTimeout()、clearTimeout()相当なだけなので、なーんも新規性は無い話でしたが、Timerクラスに辿り着くまでに案外時間が掛かりました ...
タイムアウトの別実装
RxDartパッケージのdebounce
オペレータを使った方が、スマートな場合があるかも知れません。
import 'dart:async';
import 'package:rxdart/rxdart.dart';
void main(List<String> args) {
final streamController = StreamController<String>();
final observable = Observable(streamController.stream);
listener(observable);
emitter(streamController);
}
void listener(Observable observable) {
// debounceを用い、500msの間イベントが発生しなければ呼び出される様にする
observable.debounce(Duration(milliseconds: 500)).listen((s) {
print('timeout(over 500ms): $s');
});
}
void emitter(StreamController streamController) {
Future(() {}) // async/awaitを使いませんが、理由は特にありません
.then((_) => streamController.add('400ms-1'))
.then((_) => Future.delayed(Duration(milliseconds: 400)))
.then((_) => streamController.add('600ms-1'))
.then((_) => Future.delayed(Duration(milliseconds: 600)))
.then((_) => streamController.add('400ms-2'))
.then((_) => Future.delayed(Duration(milliseconds: 400)))
.then((_) => streamController.add('600ms-2'))
.then((_) => Future.delayed(Duration(milliseconds: 600)));
}
実行すると、以下の様にコンソールに出力されます。
timeout(over 500ms): 600ms-1
timeout(over 500ms): 600ms-2