Upgrade to Pro — share decks privately, control downloads, hide ads and more …

withContextってスレッド切り替え以外にも使えるって知ってた?

 withContextってスレッド切り替え以外にも使えるって知ってた?

KotlinではCoroutineを用いることで、非同期処理を手続き的に記述できます。
Coroutineの利用例として「ネットワークアクセスをwithContext(Dispatchers.IO)でラップすることでIOスレッドで行う」という例が紹介されることが多いです。
そのため、「withContextはスレッド切り替え時に使う」という理解をされている方は多いと思います。
しかし、実際にはwithContextはCoroutineContextを切り替えるために利用するものであり、スレッド切り替え以外にもさまざまな活用法があります。
本セッションでは、CoroutineContextについて触れたうえで、筆者のチームがwithContextをどのような場面で活用しているかを紹介したいと思います。

Tasuku Nakagawa

June 17, 2024
Tweet

More Decks by Tasuku Nakagawa

Other Decks in Technology

Transcript

  1. class LoginRepository(private val responseParser: LoginResponseParser) { private const val loginUrl

    = "https://example.com/login" // ݱࡏͷεϨουΛϒϩοΫ͢ΔɺωοτϫʔΫϦΫΤετΛߦ͏ؔ਺ // ͭ·ΓɺϩάΠϯ͕׬ྃ͢Δ·ͰΞϓϦ͕ϑϦʔζ͢Δ fun makeLoginRequest(jsonBody: String): Result<LoginResponse> { val url = URL(loginUrl) (url.openConnection() as? HttpURLConnection)?.run { ... } return ... } } https://developer.android.com/kotlin/coroutines#executing-in-a-background-thread
  2. class LoginRepository(...) { ... suspend fun makeLoginRequest(jsonBody: String): Result<LoginResponse> {

    // APIݺͼग़͠ΛIO DispatcherͰ࣮ߦ͢Δ return withContext(Dispatchers.IO) { val url = URL(loginUrl) (url.openConnection() as? HttpURLConnection)?.run { ... } return ... } } } https://developer.android.com/kotlin/coroutines#use-coroutines-for-main-safety
  3. class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() { fun login(username:

    String, token: String) { viewModelScope.launch { val jsonBody = "{ username: \"$username\", token: \"$token\"}" // makeLoginRequestͷ࣮ߦ͸IOεϨουͰߦΘΕΔ // ͦͷؒɺUIεϨου͸ϒϩοΫ͞Εͳ͍ͷͰɺϢʔβૢ࡞Λड͚෇͚ΒΕΔ val result = loginRepository.makeLoginRequest(jsonBody) // ݁ՌΛUIεϨουͰॲཧ͢Δ when (result) { is Result.Success<LoginResponse> -> ... else -> ... } } } }
  4. // StringܕΛอ࣋͢ΔίϯςΩετ class CoroutineContextA(val value: String) : CoroutineContext.Element { companion

    object Key : CoroutineContext.Key<CoroutineContextA> override val key: CoroutineContext.Key<*> = Key } // IntܕΛอ࣋͢ΔίϯςΩετ class CoroutineContextB(val value: Int) : CoroutineContext.Element { companion object Key : CoroutineContext.Key<CoroutineContextB> override val key: CoroutineContext.Key<*> = Key }
  5. val contextA = CoroutineContextA("test") val contextB = CoroutineContextB(1) contextA[CoroutineContextA.Key]?.value ==

    "test" contextB[CoroutineContextA.Key] == null val contextAB = contextA + contextB contextAB[CoroutineContextA.Key]?.value == "test" contextAB[CoroutineContextB.Key]?.value == 1 val contextABMinusA = contextAB.minusKey(CoroutineContextA.Key) contextABMinusA[CoroutineContextA.Key] == null
  6. val contextA = CoroutineContextA("test") val contextB = CoroutineContextB(1) contextA[CoroutineContextA.Key]?.value ==

    "test" contextB[CoroutineContextA.Key] == null val contextAB = contextA + contextB contextAB[CoroutineContextA.Key]?.value == "test" contextAB[CoroutineContextB.Key]?.value == 1 val contextABMinusA = contextAB.minusKey(CoroutineContextA.Key) contextABMinusA[CoroutineContextA.Key] == null
  7. val contextA = CoroutineContextA("test") val contextB = CoroutineContextB(1) contextA[CoroutineContextA.Key]?.value ==

    "test" contextB[CoroutineContextA.Key] == null val contextAB = contextA + contextB contextAB[CoroutineContextA.Key]?.value == "test" contextAB[CoroutineContextB.Key]?.value == 1 val contextABMinusA = contextAB.minusKey(CoroutineContextA.Key) contextABMinusA[CoroutineContextA.Key] == null
  8. val contextA = CoroutineContextA("test") val contextB = CoroutineContextB(1) contextA[CoroutineContextA.Key]?.value ==

    "test" contextB[CoroutineContextA.Key] == null val contextAB = contextA + contextB contextAB[CoroutineContextA.Key]?.value == "test" contextAB[CoroutineContextB.Key]?.value == 1 val contextABMinusA = contextAB.minusKey(CoroutineContextA.Key) contextABMinusA[CoroutineContextA.Key] == null
  9. val contextA = CoroutineContextA("test") val contextB = CoroutineContextB(1) withContext(a) {

    withContext(b) { currentCoroutineContext()[CoroutineContextA.Key]?.value == "test" currentCoroutineContext()[CoroutineContextB.Key]?.value == 1 } currentCoroutineContext()[CoroutineContextA.Key]?.value == "test" currentCoroutineContext()[CoroutineContextB.Key] == null }
  10. jOOQͰͷτϥϯβΫγϣϯ؅ཧ val dsl: DSLContext = xxx dsl.transactionCoroutine { config ->

    config.dsl().execute("insert into table1 ...") config.dsl().execute("insert into table2 ...") }
  11. class UseCase(private val dsl: DSLContext, private val service: Service) {

    suspend fun execute() { dsl.transactionCoroutine { config -> service.execute(config.dsl()) } } } class Service(private val repo: Repository) { suspend fun execute(ctx: DSLContext) { repo.save(ctx) } } class Repository { suspend fun save(ctx: DSLContext) { ctx.execute("insert into table1 ...") } }
  12. // DSLContextΛอ࣋͢ΔElement class TransactionElement(val ctx: DSLContext) : AbstractCoroutineContextElement(TransactionElement) { companion

    object Key : CoroutineContext.Key<TransactionElement> } class TransactionManager(private val ctx: DSLContext) { suspend fun <T> execute(block: suspend () -> T): T = ctx.transactionCoroutine { config -> withContext(TransactionElement(config.dsl())) { block() } } }
  13. class Repository(private val dsl: DSLContext) { suspend fun save() {

    (coroutineContext[TransactionElement]?.ctx ?: this.ctx) .execute("insert into table1 ...") } } class UseCase( private val manager: TransactionManager, private val service: Service ) { suspend fun execute() { manager.execute { service.execute(config.dsl()) } } }
  14. class Controller(private val service: Service) { suspend fun get(session: Session)

    { val tenantId = session.getTenantId() service.execute(tenantId) } } class Service(private val repo: Repository) { suspend fun execute(tenantId: Long) { repo.select(tenantId) } } class Repository(private val ctx: DSLContext) { suspend fun select(tenantId: Long) { ctx.execute("select * table where tenant_id = ?", tenantId) } }
  15. class UserInfoElement(val tenantId: Long) : AbstractCoroutineContextElement(UserInfoElement) { companion object Key

    : CoroutineContext.Key<UserInfoElement> } suspend fun getTenantId(): Long = coroutineContext[UserInfoElement]!!.tenantId class Controller(private val service: Service) { suspend fun get(session: Session) = withContext(UserInfoElement(session.getTenantId())){ service.execute() } } class Service(private val repo: Repository) { suspend fun execute() { repo.select() } } class Repository(private val ctx: DSLContext) { suspend fun select() { ctx.execute("select * table where tenant_id = ?", getTenantId()) } }
  16. data class UserInfo(val tenantId: String) class ItemDao { context(UserInfo) fun

    selectById(itemId: Int): Item? = execute(""" select * from item where id = $itemId and tenant_id = $tenantId """) } get("/") { with(UserInfo("123456")) { call.respond(...) } }
  17. • Ϣʔβ৘ใ͸Context Receiver͕ྑͦ͞͏ ◦ શͯͷϝιουʹ͓͍ͯඞਢ ◦ ଘࡏ͢Δ͜ͱΛίϯύΠϧ࣌ʹอূ͍ͨ͠ • τϥϯβΫγϣϯ͸CoroutineContext͕ྑͦ͞͏ ◦

    ଘࡏ͢Δ͜ͱΛίϯύΠϧ࣌ʹอূ͢Δඞཁ͕ͳ͍ ◦ ʢSpring AOP͕Context ReceiverʹରԠ͍ͯ͠ͳ͍ʣ ࢖͍෼͚ https://github.com/Kotlin/KEEP/blob/context-parameters/proposals/context-parameters.md