У меня есть этот код runBlocking, и я хотел знать, гарантировано ли, что сопрограмма, запущенная через runBlocking, всегда будет выполняться в одном и том же потоке (т. е. потоке, в котором начал выполняться runBlocking)?
1-й println покажет, скажем, Thread t1. Но после выполнения задержки функции приостановки я хочу знать, возобновится ли эта сопрограмма в том же потоке t1 или ее можно будет выполнить в другом потоке потока t2? Это связано с тем, что сопрограмма может выполняться в двух или более потоках, поэтому сопрограмма, созданная с помощью runBlocking, может выполняться в потоке t1, приостанавливаться при задержке() и возобновляться в другом потоке t2.
fun main(): Unit = runBlocking(Dispatchers.Default) { // Thread t1
println("runBlocking start: ${Thread.currentThread().name}") // Thread t1
delay(3000)
println("runBlocking end: ${Thread.currentThread().name}") // is it necessary to be t1 ?
}
Вывод, который я получаю каждый раз: (но я чувствую, что во второй строке вывода это также могло быть DefaultDispatcher-worker-2): --
запуск runBlocking: DefaultDispatcher-worker-1
Конец runBlocking: DefaultDispatcher-worker-1
Да, если вы укажете многопоточный диспетчер, например Dispatchers.Default
.
Попробуйте запустить новую сопрограмму или две внутри runBlocking
:
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun main(): Unit = runBlocking(Dispatchers.Default) {
log("runBlocking start")
async { delay(50) }.await()
log("runBlocking end")
}
В конце концов, будут напечатаны разные потоки:
[DefaultDispatcher-worker-1 @coroutine#1] runBlocking start
[DefaultDispatcher-worker-2 @coroutine#1] runBlocking end
Ответ @jan-itor правильный и решил мой вопрос.
Хотя использование DefaultDispatcher каждый раз печатает один и тот же поток для моего ПК.
Я создал свой собственный CorotuineDispatcher, чтобы продемонстрировать, что эта сопрограмма также может выполняться в двух разных потоках.
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun main(): Unit = runBlocking(Executors.newFixedThreadPool(100).asCoroutineDispatcher()) {
log("runBlocking start") // [pool-1-thread-1] runBlocking start
async { delay(500) }.await()
log("runBlocking end") //[pool-1-thread-4] runBlocking end
}
@ k314159 k314159 Спасибо за ясность. Да, закрывать пользовательский диспетчер — это хорошая практика. Я забыл это сделать.
В отличие от ответа @jan-itor, вам не нужно заключать вызов delay
в async
. Если вы вызовете runBlocking
в двух отдельных потоках, вы увидите эффект только от вызова delay
:
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun main() {
repeat(2) { rep ->
thread {
runBlocking(Dispatchers.Default) {
log("$rep: runBlocking start")
delay(50)
log("$rep: runBlocking end")
}
}
}
}
Выход:
[DefaultDispatcher-worker-1] 1: runBlocking start
[DefaultDispatcher-worker-2] 0: runBlocking start
[DefaultDispatcher-worker-2] 1: runBlocking end
[DefaultDispatcher-worker-1] 0: runBlocking end
Это показывает, что при вызове runBlocking
повтора 1 начался в потоке DefaultDispatcher-worker-1
и закончился в потоке DefaultDispatcher-worker-2
, а в повторении 0 все было наоборот.
Вы можете увидеть это более четко, если запустите опцию -Dkotlinx.coroutines.debug
VM:
[DefaultDispatcher-worker-2 @coroutine#2] 1: runBlocking start
[DefaultDispatcher-worker-1 @coroutine#1] 0: runBlocking start
[DefaultDispatcher-worker-6 @coroutine#1] 0: runBlocking end
[DefaultDispatcher-worker-1 @coroutine#2] 1: runBlocking end
На моей машине ответ @jan-itor работает иногда, но в основном обе строки регистрируются в одном потоке. Ваш код каждый раз показывает журналы в разных потоках. Кроме того, с вашим кодом вам не нужно звонить
async
: просто вызватьdelay
тоже подойдет. Однако обратите внимание, что ваша программа не завершается. В конце вам нужно закрыть исполнителя.