2020年12月26日土曜日

Dispatchers.Main.immediate ってなに?

Dispatchers.Main は MainCoroutineDispatcher です。
  1. package kotlinx.coroutines  
  2. ...   
  3.   
  4. public actual object Dispatchers {  
  5.     ...  
  6.     @JvmStatic  
  7.     public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher  
  8.     ...  
  9. }  
Dispatchers.Main.immediate は MainCoroutineDispatcher に定義されており、Dispatchers.Main.immediate 自体も MainCoroutineDispatcher です。
  1. package kotlinx.coroutines  
  2. ...   
  3.   
  4. public abstract class MainCoroutineDispatcher : CoroutineDispatcher() {  
  5.     ...  
  6.     public abstract val immediate: MainCoroutineDispatcher  
  7.     ...  
  8. }  
kotlinx-coroutines-android では HandlerDispatcher が MainCoroutineDispatcher を継承し、
  1. package kotlinx.coroutines.android  
  2. ...  
  3. public sealed class HandlerDispatcher : MainCoroutineDispatcher(), Delay {  
  4.     ...  
  5.     public abstract override val immediate: HandlerDispatcher  
  6. }  
HandlerContext が HandlerDispatcher を継承しています。
  1. package kotlinx.coroutines.android  
  2. ...   
  3.   
  4. internal class HandlerContext private constructor(  
  5.     private val handler: Handler,  
  6.     private val name: String?,  
  7.     private val invokeImmediately: Boolean  
  8. ) : HandlerDispatcher(), Delay {  
  9.     ...  
  10.     @Volatile  
  11.     private var _immediate: HandlerContext? = if (invokeImmediately) this else null  
  12.   
  13.     override val immediate: HandlerContext = _immediate ?:  
  14.         HandlerContext(handler, name, true).also { _immediate = it }  
  15.     
  16.     override fun isDispatchNeeded(context: CoroutineContext): Boolean {  
  17.         return !invokeImmediately || Looper.myLooper() != handler.looper  
  18.     }  
  19.     ...  
  20. }  
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 を返すようになっています。
  1. package kotlinx.coroutines  
  2. ...   
  3.   
  4. public abstract class CoroutineDispatcher :  
  5.     AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {  
  6.     ...  
  7.     public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true  
  8.     ...  
  9. }  


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

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


viewModelScope, lifecycleScope は dispatcher として Dispatchers.Main.immediate が指定されています。
  1. val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope  
  2.     get() = lifecycle.coroutineScope  
  3.     
  4. val Lifecycle.coroutineScope: LifecycleCoroutineScope  
  5.     get() {  
  6.         while (true) {  
  7.             ...  
  8.             val newScope = LifecycleCoroutineScopeImpl(  
  9.                 this,  
  10.                 SupervisorJob() + Dispatchers.Main.immediate  
  11.             )  
  12.             ...  
  13.         }  
  14.     }  
  1. val ViewModel.viewModelScope: CoroutineScope  
  2.         get() {  
  3.             ...  
  4.             return setTagIfAbsent(JOB_KEY,  
  5.                 CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))  
  6.         }  




0 件のコメント:

コメントを投稿