How to make Kotlin's LifecycleScope has sequential behaviour as Java's Executors.newSingleThreadExecutor?

huangapple go评论69阅读模式
英文:

How to make Kotlin's LifecycleScope has sequential behaviour as Java's Executors.newSingleThreadExecutor?

问题

In Java Android, 为了在不阻塞主线程的情况下实现顺序行为,我正在使用以下代码。

Java Android

private static final ExecutorService executor = Executors.newSingleThreadExecutor();

executor.execute(() -> task0());
executor.execute(() -> task1());
executor.execute(() -> task2());

上述代码将始终按照task0task1task2函数的确切顺序执行,不管函数内部发生了什么。

我对Kotlin的LifecycleScope提供的生命周期感知功能印象深刻。我尝试以以下Kotlin的LifecycleScope形式编写代码。

Kotlin Android

val dispatcherForCalendar = Executors.newSingleThreadExecutor().asCoroutineDispatcher()

lifecycleScope.launch(dispatcherForCalendar) {
    task0()
}
lifecycleScope.launch(dispatcherForCalendar) {
    task1()
}
lifecycleScope.launch(dispatcherForCalendar) {
    task2()
}

上述代码将按照task0task1task2函数的确切顺序执行,除非在函数中执行了延迟操作。

  1. 实际上,我不会在任务函数中显式插入delay代码。在这种情况下,Android系统是否仍然可以隐式执行delay操作?
  2. 如何实现与我的Java代码Executors.newSingleThreadExecutor相同的顺序行为?

谢谢。

英文:

In Java Android, to achieve sequential behavior without blocking main thread, this is the code I am using.

Java Android

private static final ExecutorService executor = Executors.newSingleThreadExecutor();

executor.execute(() -> task0());
executor.execute(() -> task1());
executor.execute(() -> task2());

The above code, will always execute in the exact order of task0, task1 and task2 functions regardless what is happening inside the functions.

I am impressed by the life cycle aware feature, offered by Kotlin's LifecycleScope. I try to write the code in the following Kotlin's LifecycleScope form.

Kotlin Android

val dispatcherForCalendar = Executors.newSingleThreadExecutor().asCoroutineDispatcher()

lifecycleScope.launch(dispatcherForCalendar) {
    task0()
}
lifecycleScope.launch(dispatcherForCalendar) {
    task1()
}
lifecycleScope.launch(dispatcherForCalendar) {
    task2()
}

The above code will executed in the exact order of task0, task1 and task2 functions, except when delay is performed in the functions.

  1. In reality, I will not insert delay code explicitly in task functions. In such a case, can Android system still perform delay operation implicitly?
  2. How I can achieve sequential behavior, same as my Java code Executors.newSingleThreadExecutor?

Thank you.

答案1

得分: 1

Regarding 1):

协程可以在任何suspend函数调用时将线程返回给调度程序,而不仅仅是在delay()调用时。

Regarding 2):

我的答案在这里展示了如何使用通道(Channel)构建一个顺序任务队列。您可以在构造函数中注入一个CoroutineScope,以便在Activity或Fragment中使用时可以传递lifecycleScope。类似这样:

class JobQueue(
    private val scope: CoroutineScope,
    private val defaultContext: CoroutineContext = Dispatchers.Main
) {
    private val queue = Channel<Job>(Channel.UNLIMITED)

    init {
        scope.launch(Dispatchers.Default) {
            for (job in queue) job.join()
        }
    }

    fun submit(
        context: CoroutineContext = defaultContext,
        block: suspend CoroutineScope.() -> Unit
    ) {
        synchronized {
            val job = scope.launch(context, CoroutineStart.LAZY, block)
            queue.trySend(job)
        }
    }
}

如果这些是阻塞的任务,您可以例如将Dispatchers.IO作为运行任务的defaultContext

英文:

Regarding 1):

The coroutines may yield the thread back to the dispatcher at any suspend function call, not just at delay() calls.

Regarding 2):

My answer here shows how you can build a sequential task queue using a Channel. You could modify the class to allow a CoroutineScope to be injected in the constructor, so lifecycleScope could be passed in when using it in an Activity or Fragment. Something like this:

class JobQueue(
    private val scope: CoroutineScope,
    private val defaultContext: CoroutineContext = Dispatchers.Main
) {
    private val queue = Channel&lt;Job&gt;(Channel.UNLIMITED)

    init { 
        scope.launch(Dispatchers.Default) {
            for (job in queue) job.join()
        }
    }

    fun submit(
        context: CoroutineContext = defaultContext,
        block: suspend CoroutineScope.() -&gt; Unit
    ) {
        synchronized { 
            val job = scope.launch(context, CoroutineStart.LAZY, block)
            queue.trySend(job)
        }
    }
}

If these were blocking jobs, you might for example pass Dispatchers.IO as the defaultContext for running the jobs.

huangapple
  • 本文由 发表于 2023年6月22日 00:50:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76525521.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定