kotlin 当我同时应用Flow和CoroutineScope时,我会遇到麻烦吗?

ghhkc1vu  于 2023-05-07  发布在  Kotlin
关注(0)|答案(1)|浏览(171)

为了避免在主线程中的繁重工作,我将.flowOn(Dispatchers.IO)应用于class TranslateIncompleted中的Flow listIncompleted
你知道,有时当其他用户调用suspend函数时,他在CoroutineScope(Dispatchers.IO) .launch {...}中调用它。
1:当我同时应用FlowCoroutineScope时,我会遇到麻烦吗?
2:如果只能使用一个,使用Flow.flowOn(Dispatchers.IO)和使用CoroutineScope(Dispatchers.IO)哪种方式更好?

class ServiceTranslate: Service() {

    @Inject lateinit var translateIncompleted: ITranslateIncompleted
    private var job: Job? = null

    override fun onCreate() {
        super.onCreate()
        job = CoroutineScope(Dispatchers.IO) .launch {
            translateIncompleted.translateIncompletedAndUpdate()
        }
    }
  
    override fun onDestroy() {
       super.onDestroy()
       job?.cancel()
    }
    ...
}

class TranslateIncompleted @Inject constructor(
   ...
): ITranslateIncompleted {

    override suspend fun translateIncompletedAndUpdate() {

        val listIncompleted = handleMInfo.listIncompleted()        
        listIncompleted
            .flowOn(Dispatchers.IO)
            .collect {
               ...
            }
    }
}

新增内容:

致天福04:谢谢!
A:我对代码做了一些修改。现在看起来好吗?
B:我不相信onEach是块函数,但collect是。我希望collect在流程发生变化时保持运行和处理数据。由于onEach只运行一次,我认为它不适合这种特殊情况,对吗?
C:为什么在Flow上指定Dispatchers.IO是一个糟糕的设计?如果我在一个Flow上指定Dispatchers.IO,那么无论用什么方法调用这个流,我都可以保证在Dispatchers.IO线程中运行的工作量。

nhaq1z21

nhaq1z211#

使用CoroutineScope(…).launch是一种代码气味,因为如果您不打算将其分配给属性以便在适当的时候取消它,则不应该创建新的协程作用域。(您可以将返回的Job分配给某个属性,然后在适当的时候取消该属性,但也可以使用GlobalScope。)
如果使用LifecycleService而不是Service作为超类,那么可以使用lifecycleScope来启动协程。但是如果你没有,你应该创建一个带有SupervisorJob的CoroutineScope,并在onDestroy()中取消它。
如果您正在启动一个无论您的应用是否在哪个屏幕或服务中都不能取消的协程,那么您可以使用GlobalScope,但必须注意不要捕获可能导致内存泄漏的引用。
很少需要在从其他地方获得的流上指定Dispatchers.IO。如果其他一些类公开共享阻塞的Flow,这将是非常糟糕的设计,并且违反惯例。据我所知,Android、Google或Square的库都没有这样做。你应该使用flowOn的唯一地方是在你自己的 blocking 代码 added 到Flow操作符中的Flow之后,比如在onEach块中。
通常,如果我在协程中所做的只是收集一个Flow,那么我根本不使用launch。您可以在流上使用onEachlaunchIn,以获得更清晰的语法。
编辑:
A)现在看起来没问题,因为您确保在onDestroy中取消了作业。您可以通过使用val CoroutineScope属性而不是var Job来使其更加防错。但是没有必要指定一个调度器,因为你的协程所做的唯一事情就是调用一个suspend函数。
我不知道你到底想说什么。onEach是一个操作符。它不会阻塞或挂起,但是你传递给它的lambda会挂起,并且会为每个上游发射重复调用,就像传递给collect的lambda会为每个上游发射重复调用一样。你不能在onEach中调用阻塞代码,除非你在它后面加上flowOncollect也不会阻塞。它暂停。
我想也许你还没有清楚地了解这三类同步功能之间的区别:阻塞功能、挂起功能和两者都不做的功能。所有的Flow操作符(包括中间操作符,如onEach/map和终端操作符,如collect/first()/launchIn/stateIn)都是非阻塞和非挂起的,但是你传递给它们的lambda表达式 * 是 * 挂起的。它们不应该在lambdas中包含阻塞代码,除非您确保在可以处理阻塞代码的适当分派器上调用它们。flowOn改变了前面(更高)操作符中代码的上下文,但不能影响下游操作符,包括collect
C)这不是我说的。我说过,您应该能够假设从另一个类中检索到的流不会阻塞,因为如果阻塞了,那么另一个类的设计者就为您设置了一个违反约定的陷阱。如果你是一个类的设计者,这个类创建了一个要传递给其他类的流,那么你知道你的阻塞代码在哪里,哪个调度器是合适的,这样你就可以在与其他类共享之前在内部用flowOn修复它。
你想要哪一个?
API中的代码A:

/** Do anything you want with this flow. It is safe. */
fun retrieveAFlow() : Flow<Something> ...

API中的代码B:

/** Be very careful with this flow. It breaks convention and will block 
  a thread if you don't use a dispatcher that is safe for consuming IO 
  blocking work, so you have to collect it in a context that uses such a 
  dispatcher. Don't use Dispatchers.Default, though because this is IO 
  blocking work, not CPU blocking work! Hopefully you noticed this 
  documentation and read it very carefully and don't make a mistake 
  following these directions, or your app will not behave correctly! */
fun retrieveAFlow() : Flow<Something> ...

相关问题