【问题标题】:Handle cancelation inside Kotlin Coroutines producer在 Kotlin Coroutines 生产者内部处理取消
【发布时间】:2018-06-25 18:22:59
【问题描述】:

是否可以在生产者构建器内部处理生产者取消?取消订阅回调可能很有用:

private fun changes(key: String) = produce<Unit>(UI, CONFLATED) {
        val listener = OnSharedPreferenceChangeListener { _, changedKey ->
             if (key == changedKey) offer(Unit)
        }
        prefs.registerOnSharedPreferenceChangeListener(listener)
        ???.onCancel { 
                 prefs.unregisterOnSharedPreferenceChangeListener(listener)
        }
}

或者可能存在另一种实现这种情况的方式?

【问题讨论】:

    标签: android kotlin kotlin-coroutines


    【解决方案1】:

    首先,您不应该使用produce builder 以这种方式与侦听器适配API,因为在produce builder 主体存在时,通道会立即关闭并停止提供其功能。相反,您应该只创建一个Channel() 并创建相应的连接。

    不幸的是,频道目前不提供开箱即用的方式来安装取消侦听器(请参阅issue #341)。在频道关闭时立即得到通知的唯一方法是扩展相应的频道类,这导致以下代码:

    private fun changes(key: String): ReceiveChannel<Unit> = object : ConflatedChannel<Unit>() {
        val listener = OnSharedPreferenceChangeListener { _, changedKey ->
            if (key == changedKey) offer(Unit)
        }
    
        init {
            prefs.registerOnSharedPreferenceChangeListener(listener)
        }
    
        override fun afterClose(cause: Throwable?) {
            prefs.unregisterOnSharedPreferenceChangeListener(listener)
        }
    }
    

    【讨论】:

    • ConflatedChannel 好像是internal,所以我们不能使用它...
    【解决方案2】:

    即将推出的 kotlinx.coroutines 库 should expose a Channel.invokeOnClose { ... } method 版本可以满足此类用例。

    但是,在此期间有一些解决方案可以解决此问题。 一种解决方案是subclass the channel you're looking for, as Roman Elizarov suggested.

    另一种解决方案是使用以下方式生成:

    fun SharedPreferences.changes(key: String) = produce {
        val changesChannel = ConflatedChannel<Unit>()
        val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, changedKey ->
            if (key == changedKey) changesChannel.offer(Unit)
        }
        registerOnSharedPreferenceChangeListener(listener)
        try {
            for (change in changesChannel) {
                send(change)
            }
        } finally {
            unregisterOnSharedPreferenceChangeListener(listener)
        }
    }
    

    【讨论】:

    • 这是可行的解决方案,但在这种情况下使用produce 似乎在语义上不正确。这只是很多开销,没有任何帮助。
    • 我可以告诉你,上面的代码可以按照你想要的方式工作,I used the same approach for a BLE scanning channel。那里的开销并不多,您使用一个额外的通道,就是这样。如果您真的对此感到担忧,我鼓励您对您的项目进行分析,看看这是否会比其他可以在代码库中优化的东西带来显着的性能/效率问题。 ;-) 无论如何,即将推出的invokeOnClose { ... } 方法将是最好的解决方案。
    • 虽然在频道关闭后您确实可能会在一段时间内收听 SharedPreferences,但如果没有任何变化,不是吗?如果内部通道(侦听器)没有推送任何内容,则外部通道不会发送任何内容,因此您永远不会检测到它已被关闭。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-29
    • 2021-04-30
    • 2019-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多