2020年12月26日土曜日

Dispatchers.Main.immediate ってなに?

Dispatchers.Main は MainCoroutineDispatcher です。 package kotlinx.coroutines ... public actual object Dispatchers { ... @JvmStatic public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher ... } Dispatchers.Main.immediate は MainCoroutineDispatcher に定義されており、Dispatchers.Main.immediate 自体も MainCoroutineDispatcher です。 package kotlinx.coroutines ... public abstract class MainCoroutineDispatcher : CoroutineDispatcher() { ... public abstract val immediate: MainCoroutineDispatcher ... } kotlinx-coroutines-android では HandlerDispatcher が MainCoroutineDispatcher を継承し、 package kotlinx.coroutines.android ... public sealed class HandlerDispatcher : MainCoroutineDispatcher(), Delay { ... public abstract override val immediate: HandlerDispatcher } HandlerContext が HandlerDispatcher を継承しています。 package kotlinx.coroutines.android ... internal class HandlerContext private constructor( private val handler: Handler, private val name: String?, private val invokeImmediately: Boolean ) : HandlerDispatcher(), Delay { ... @Volatile private var _immediate: HandlerContext? = if (invokeImmediately) this else null override val immediate: HandlerContext = _immediate ?: HandlerContext(handler, name, true).also { _immediate = it } override fun isDispatchNeeded(context: CoroutineContext): Boolean { return !invokeImmediately || Looper.myLooper() != handler.looper } ... } HandlerContext では immediate にセットされる HandlerContext は invokeImmediately プロパティが true になる、ということがわかります。

invokeImmediately は isDispatchNeeded() で使われます。isDispatchNeeded() の実装をみると、invokeImmediately が false のときは常に isDispatchNeeded() が true を返すことがわかります。また Looper.myLooper() != handler.looper のときも isDispatchNeeded() が true を返すことがわかります。つまり、invokeImmediately が true かつ Looper.myLooper() == handler.looper のときだけ isDispatchNeeded() は false を返します。

このことから、immediate にセットされる HandlerContext では、Looper.myLooper() が handler.looper と同じだと isDispatchNeeded() が false を返すということがわかります。

isDispatchNeeded() は coroutine を dispatch メソッドで実行するべきかどうか判定するときに呼ばれます。デフォルトは true を返すようになっています。 package kotlinx.coroutines ... public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { ... public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true ... }

つまり、(kotlinx-coroutines-android の) Dispatchers.Main.immediate はすでに UI スレッドにいる場合(現在の Looper.myLooper() が handler.looper と同じ場合)そのまますぐに実行される Dispatcher ということです。

例えば Dispatchers.Main を使った以下のコードだと class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) CoroutineScope(Dispatchers.Main).launch { println("1 : ${Thread.currentThread().name}") } println("2 : ${Thread.currentThread().name}") } } 出力は 2 が 1 より先になります。 I/System.out: 2 : main I/System.out: 1 : main CoroutineScope の dispatcher を Dispatchers.Main.immediate に変えると class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) CoroutineScope(Dispatchers.Main.immediate).launch { println("1 : ${Thread.currentThread().name}") } println("2 : ${Thread.currentThread().name}") } } 1 が先に出力されるようになります。 I/System.out: 1 : main I/System.out: 2 : main UI スレッドにいない場合はすぐには実行されず dispatch されます。 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) CoroutineScope(Dispatchers.Default).launch { println("3 : ${Thread.currentThread().name}") CoroutineScope(Dispatchers.Main.immediate).launch { println("1 : ${Thread.currentThread().name}") } println("4 : ${Thread.currentThread().name}") } println("2 : ${Thread.currentThread().name}") } } I/System.out: 2 : main I/System.out: 3 : DefaultDispatcher-worker-2 I/System.out: 4 : DefaultDispatcher-worker-2 I/System.out: 1 : main

viewModelScope, lifecycleScope は dispatcher として Dispatchers.Main.immediate が指定されています。 val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope get() = lifecycle.coroutineScope val Lifecycle.coroutineScope: LifecycleCoroutineScope get() { while (true) { ... val newScope = LifecycleCoroutineScopeImpl( this, SupervisorJob() + Dispatchers.Main.immediate ) ... } } val ViewModel.viewModelScope: CoroutineScope get() { ... return setTagIfAbsent(JOB_KEY, CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)) }



0 件のコメント:

コメントを投稿