Uzzu::Blog

Software Design, and my life.

Coroutinesの多重呼び出しの防止

Rxの世界ではHot Observableをよく使っていたけど、Coroutinesでも使いたい。 直近自分には2つの需要があるかなというところ。

  • 多重に launch しない (GUIの連打防止をGUIの実装上でやるのが面倒)
  • 多重に suspend functionを呼び出したくはないが、結果だけは欲しい

というわけで作ってみました。

叩きのコードは以下

多重launch防止

実行してみると、launchによって開始されたCoroutineが実行中間は同じCoroutineがあるので呼び出されず、結果も一度だけ返却されています。

call[0]
hot jobs count: 0
[suspendFun5] launch
callCount: 1
call[1]
hot jobs count: 1
call[2]
hot jobs count: 1
call[3]
hot jobs count: 1
call[4]
hot jobs count: 1
call[5]
hot jobs count: 1
result: 25
[suspendFun5] completed
call[6]
hot jobs count: 0
[suspendFun5] launch
:

多重にsuspend functionを呼び出さないが、結果だけは欲しい

実行してみると、実行中のsuspend functionがある場合に実行中のsuspend functionが使い回され、完了すると、中断していたCoroutineすべての処理が結果を受け取りつつ再開されます。

call[0]
channel created
call[1]
channel reused
interactor: 1
call[2]
channel reused
call[3]
channel reused
call[4]
channel reused
call[5]
channel reused
call[6]
channel sent
call[5]view result: 25
call[0]view result: 25
call[1]view result: 25
call[2]view result: 25
call[3]view result: 25
call[4]view result: 25
channel created
interactor: 2
call[7]
channel reused
call[8]
channel reused
:

内部的にBroadcastChannelを使っていますが、使用する側からすれば特にChannelを使用している意識する事なく結果だけが受け取れます。 Channelはいうて難しいので使っている事を意識したくないです。

本当に何も意識しなくていいのか?残念ながらそう簡単にもいかなくて、BroadcastChannelの購読は順序が保証されないという点を意識しなければいけません。これは長年UI開発やってきた人々からすると、 Event(Listener|Emitter|Dispatcher) の挙動を想像する(購読を開始した順に通知されてほしい)ので、自然ではない。呼び出し順序が保証されたBroadcastChannelが作れればいいのかな。とりあえず自分の需要としてはこれは大丈夫そうなので放置します。

ライブラリ化

さすがにこのままでは使い物にならないので、整理しつつライブラリ化します。

毎度 MutableMapMutex を引数に渡すのは億劫すぎるので、これらは CoroutineContext.Element にしてしまい、suspend function内から参照できるようにし、各メソッドはCoroutineScopeのExtensionとして用意します。

というわけで、できました。

https://github.com/uzzu/kortex

どういう時に使うか

たとえばOkHttpのAuthenticator上でsuspend functionを用いて再度認証をした上で復帰する場合、 runBlocking を用いる事になると思いますが、 runBlocking はあくまでも現在の実行スレッドをブロックするCoroutineである事、okhttpのDispatcherは指定次第では並列実行される為、 runBlocking のみを前提とした Authenticator#authenticate 並列実行を考慮する必要があり、その実装は複雑になりがちです。

こうする事で、(既にキューに積まれたリクエストについてはunauthorizedにならざるを得ないのでどうしようもないですが)少なくともAuthenticator#authenticate は並列実行されないようになり、多少はシンプルに記述できるようになるでしょう。かつ新しい認証処理の結果はすべてのunauthorizedなリクエストに適用されます。便利そうですね。

どうぞ。

comments powered by Disqus