非同期関数(ここでは主にasyncで作られるFutureなもの)を途中で止めるにはどうしたらいいのか。
例えば、FlutterのページでFutureの結果を使って表示する状況では、その取扱いについてはある程度決まっている。例えばFlutter-非同期関数(async)の結果をウィジェットに表示する方法を参照。
そこで疑問に思ったのが、Futureが完了していない状態でページをpopなどで離れた場合、完了していないFutureはどうなっているのかというところ。
- 答えは「非同期状態で動き続ける」。
つまり、popでページを戻してもFutureな関数は動き続けているのである。
動き続けるFuture
例えば以下の様なページ。
このページに入るとgenerateSupplyという非同期関数が動作するのだが、定期的にデバッグ用のプリントを表示するようにしているものになっている。
ページに入ってすぐに戻っても、非同期関数は動き続け、デバッグ用のプリント出力し続けることが分かると思う。
「CLOSE TO ROUTE」がこのページから抜けたことを表しているのだけど、それ以降もプリントし続ける。
このような非同期関数を実装を変えずに外部から止める方法はあるのか。
- ありません。
以下の様にIssuesにあげられているのだけど、たぶん対応はされないだろうという内容で締めくくられている。
上のIssueを読む前に、止める方法があるのか検証した結果が以下の物。
timeoutを使ってみる
Futureにtimeoutというのがあって、これを使えば非同期関数が何らかの方法で止められるのではないかと思った。
作ったサンプルはこちら。
想定していた動きは、timeoutで指定した時間が過ぎたらFutureのthenあたりが呼び出されるかなと思ったのだけど、結果としては非同期関数は動き続けている。
timeoutはFutureを監視しているわけではなく、指定した時間までにFutureが完了していないと、onTimeoutで指定した値を返すといったもので、裏ではFutureは動き続ける。
asyncパッケージのCancelableCompleter
stackoverflowなどを見ると、キャンセルしたいのであればこれを使え、と書かれているのだけど、サンプルはないけどこちらも基本的にFuture(非同期関数)を止めるわけではない。
意味合い的にはasync中のCompleterにキャンセルという属性を持たせたようなものだった。
そのため、こちらもキャンセルを呼び出してもFuture自体は動き続けている。
非同期関数(Future)を止めるには
非同期関数内でキャンセルするかどうかのフラグをチェックして、フラグがTrueの場合に関数から抜ける(などの処理を行う)というのが正しいやり方になる。
サンプルはこちら。
asyncパッケージを使用しているのでDartPadは使えないので、コードだけ。
[0]で出力しているのは全く止めていないもの。
[1]は途中で止めるのだけど、その方法としてパラメータで渡したList<bool>を使ったやり方。
この方法は、非同期関数単独の場合に、外側で用意したList<bool>を通してfor文から抜け出すという方法。
[3]はCancelableCompleterを使ったやり方。
実際にはクラス化してcancelメソッドを呼び出した場合にキャンセルフラグが立ったのを非同期関数で見るようにしている点。
CancelableCompleterでなくてもクラス化した中でフラグ等を利用するやり方でも問題はないと思う。
補足
上記の様にフラグを保持してFutureを止めるという方法を実行できるのだけど、それはフラグを検知可能な範囲でのこと。
例えば、非同期関数内で呼び出している非同期関数に関しては止めることができない。
そのため、止めたいような非同期関数、例えば処理時間がかかるようなものについてはIsolateを使うのが良いのではないかという結論になる。
Isolateを使って処理中の関数を止める
結局、何が何でも止めるのだ、というのにはIsolateを使ってスレッド自体を止めてしまうという方法しかなかった。
標準のIsolateを使うと実装が面倒なので、そこは既存のパッケージを使う。
一番よさそうなのがworker_manager。
Executorのexecuteで実行する関数を設定することで、その関数を呼び出せる。
リターンで取得したCancelableにはcancelメソッドがあるので、それを呼び出すことで実行中の関数が途中で停止することができる。
別スレッドでの実行なので、メインスレッド側で持つ情報のやり取りなどが面倒だけど、停止可能ということに関しては使うメリットはあるかも。
コメント