Почему и как сопрограмма Kotlin предотвращает блокировку потока, даже без ключевого слова «suspend»? - PullRequest
3 голосов
/ 20 октября 2019

Я столкнулся с неожиданным поведением при использовании сопрограмм в приложении для Android.

Предположим, у меня есть следующая функция, которая не "приостановить". Он запускает рабочие потоки и должен блокировать вызывающий поток до тех пор, пока все рабочие не прекратят работу:

fun doSomething() : Result {

    // producers init thread
    Thread {
        for (i in 0 until NUM_OF_MESSAGES) {
            startNewProducer(i) // each producer is a thread
        }
    }.start()

    // consumers init thread
    Thread {
        for (i in 0 until NUM_OF_MESSAGES) {
            startNewConsumer() // each consumer is a thread
        }
    }.start()


    synchronized(lock) {
        while (numOfFinishedConsumers < NUM_OF_MESSAGES) {
            try {
                (lock as java.lang.Object).wait()
            } catch (e: InterruptedException) {
                return@synchronized
            }
        }
    }

    synchronized(lock) {
        return Result(
                System.currentTimeMillis() - startTimestamp,
                numOfReceivedMessages
        )
    }

}

Я знаю, что (lock as java.lang.Object).wait() некрасиво, и мне было бы лучше с ReentrantLock, но я намеренно хотел попасть насамый примитивный уровень.

Теперь, если я выполняю эту функцию без сопрограммы из основного потока Android, она блокирует вызывающий поток (ожидаемое поведение):

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    someObject.doSomething()
}

Однако, если я просто оберну еев сопрограмме, которая также выполняется в главном потоке, основной поток больше не блокируется, но функциональность остается прежней:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    CoroutineScope(Dispatchers.Main).launch {
        val result = someObject.doSomething()
    }
}

Два вопроса:

  1. Я думал, чточтобы сопрограммы работали, функции должны быть «приостановлены», но здесь это не так. Итак, какой смысл тогда «приостанавливать»?
  2. Вызов (lock as java.lang.Object).wait() должен был заблокировать основной поток. Как получается, когда сопрограмма задействована? У сопрограмм есть способ «перехватывать» такие низкоуровневые взаимодействия?

Спасибо

1 Ответ

7 голосов
/ 20 октября 2019

Как и post() для View, launch() (обычно) планирует работу, которая должна выполняться асинхронно относительно текущего бита выполнения. Таким образом, код в вашем лямбда-выражении, переданный launch(), будет в конечном итоге выполняться в главном потоке приложения, точно так же, как Runnable, который вы указали для post(), будет в конечном итоге выполняться в главном потоке приложения. Тем не менее, ваша onCreate() функция будет продолжаться после точки launch(), чтобы делать все, что она должна.

Однако, точно так же, как Runnable, переданный в post(), может все еще связать главноепоток приложения, из-за того, что он делает в run(), ваша сопрограмма все еще может связать основной поток приложения. Просто эта работа произойдет позже, чем если бы вы выполняли ее непосредственно в onCreate().

Просто анимации все еще работают

IIRC, на более новыхверсии Android, сами анимации обрабатываются в отдельном потоке рендеринга.

...