9
3

More than 5 years have passed since last update.

(小ネタ) Timerクラスでタイムアウト処理を行う

Last updated at Posted at 2018-12-09

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のインスタンスへの参照が保持されていません。

Future.delayed
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
9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3