kotlinx.coroutines.delay()是一个挂起函数。它不会阻塞当前线程。Thread.sleep()阻塞当前线程。Thread.sleep()这意味着该线程中的其他代码在退出之前不会执行。
示例 1 - kotlinx.coroutines.delay()
fun main(args: Array<String>) {
runBlocking {
run()
}
}
}
suspend fun run() {
coroutineScope {
val timeInMillis = measureTimeMillis {
val mainJob = launch {
//Job 0
launch {
print("A->")
delay(1000)
print("B->")
}
//Job 1
launch {
print("C->")
delay(2000)
print("D->")
}
//Job 2
launch {
print("E->")
delay(500)
print("F->")
}
//Main job
print("G->")
delay(1500)
print("H->")
}
mainJob.join()
}
val timeInSeconds =
String.format("%.1f", timeInMillis/1000f)
print("${timeInSeconds}s")
}
}
Main Job将首先运行,然后将被delay()挂起函数挂起,然后是Job 0 -> Job 1 -> Job 2。所有Job都在大约同一时间暂停和启动。然后,最短的delay()将首先运行。timeinSeconds完成所有工作的时间最长应为delay()2 秒。
输出如下所示:
G->A->C->E->F->B->H->D->2.0s
这很容易理解。如果我们替换delay(2000)为Thread.Sleep(2000)for Job1怎么办?
示例 2 - Dispatchers.Main 上的 Thread.sleep()
suspend fun run() {
coroutineScope {
val timeInMillis = measureTimeMillis {
val mainJob = launch {
//Job 0
launch {
print("A->")
delay(1000)
print("B->")
}
//Job 1
launch {
print("C->")
Thread.sleep(2000)
print("D->")
}
//Job 2
launch {
print("E->")
delay(500)
print("F->")
}
//Main job
print("G->")
delay(1500)
print("H->")
}
mainJob.join()
}
val timeInSeconds =
String.format("%.1f", timeInMillis/1000f)
print("${timeInSeconds}s")
}
}
与上面的示例 1 类似,MainJob将首先运行并由delay()suspend 函数挂起,然后是Job 0 → Job 1。 Job 0将被暂停。但是,当在Job 1Thread.sleep(2000)上运行时,线程将被阻塞 2 秒。此时Job 2未执行。
2 秒后,将首先打印出D ,然后是Job 2中的E。然后Job 2将被暂停。因为Main Job和Job 0被挂起不到 2 秒,它会立即运行。Job 0将首先运行,因为挂起时间更短。
0.5 秒后,Job 2恢复并完成。它将打印出F。
时间戳 1(0 秒后)
- MainJob和Job 0启动和暂停。
- Job 1启动并阻塞线程
时间戳 2(2 秒后)
- Job 1已完成
- Job 2已启动并暂停。
- Job 0和MainJob恢复并完成。
时间戳 3(0.5 秒后)
- Job 3恢复并完成
所以总时间消耗在 2.5 秒左右。
输出如下所示:
G->A->C->D->E->B->H->F->2.5s
示例 3 - Dispatchers.Default/IO 上的 Thread.sleep()
等等,如果在后台线程中使用or运行run挂起函数会怎样。例如:Dispatchers.DefaultDispatchers.IO
runBlocking {
withContext(Dispatchers.Default) {
run()
}
}
输出变成这样:
A->C->G->E->F->B->H->D->2.0s
输出类似于上面的示例 1,其中Thread.sleep()似乎没有阻塞线程!为什么?
使用Dispatchers.Defaultor时Dispatchers.IO,它由线程池支持。每次我们调用时launch{},都会创建/使用不同的工作线程。
例如,这里是正在使用的工作线程:
- Main Job - DefaultDispatcher-worker-1
- Job 0 - DefaultDispatcher-worker-2
- Job 1 - DefaultDispatcher-worker-3
- Job 2 - DefaultDispatcher-worker-4
要查看当前正在运行的线程,您可以使用println(“Run ${Thread.currentThread().name}”)
所以Thread.sleep()确实阻塞了那个线程,但只阻塞了 DefaultDispatcher-worker-3. 其他Job仍然可以继续运行,因为它们在不同的线程上。
时间戳 1(0 秒后)
- 启动Main Job、Job 0、Job 1和Job 2。顺序可以是随机的。见下文注(1)。
- Main Job、Job 0和Job2被挂起。
- Job 1阻塞了它自己的线程。
时间戳 2(0.5 秒后)
- Job 2恢复并完成。
时间戳 3(1 秒后)
- Job 0恢复并完成
时间戳 4(1.5 秒后)
- 主要工作恢复完成
时间戳 5(2 秒后)
- Job 1恢复并完成
因为每个Job都在不同的线程上运行,所以Job可以在不同的时间启动。所以 A、C、E、G 的输出可以是随机的。因此,您会看到 initiat Job的启动顺序与上面的示例 1 中的不同。
何时使用 Thread.Sleep()?
Thread.Sleep()几乎没用,因为大多数时候我们不想阻塞线程。kotlinx.coroutines.delay()被推荐。
我个人Thread.Sleep()用来模拟长时间运行的阻塞线程的任务。测试我是否已将长时间运行的任务放入后台线程很有用。如果我从主 UI 线程运行它,则 UI 将不会响应。
如果我simulateBlockingThreadTask()在主 UI 线程中调用它,它将阻塞主 UI 线程。应用程序将因 UI 无响应而崩溃。
private suspend fun simulateBlockingThreadTask() {
Thread.sleep(2000)
}
但是,如果我们使用 将线程切换到后台线程kotlinx.coroutines.withContext(),则从主 UI 线程调用它simulateBlockingThreadTask()不会使应用程序崩溃。
private suspend fun simulateBlockingThreadTask() {
withContext(Dispatchers.Default) {
Thread.sleep(2000)
}
}
使用 yield()
通常最好不要长时间阻塞 UI 线程。
在代码示例中,我模拟了阻塞和非阻塞线程任务。总运行时间为 400 毫秒。
private suspend fun simulateLongRunningTask() {
simulateBlockingThreadTask()
simulateNonBlockingThreadTask()
}
private suspend fun simulateBlockingThreadTask() {
repeat(10) {
Thread.sleep(20)
yield()
}
}
private suspend fun simulateNonBlockingThreadTask() {
delay(200)
}
结论
Thread.sleep()阻塞线程并且kotlinx.coroutines.delay()不会。
我Thread.sleep()用来测试我是否已正确地将长时间运行的任务放入后台线程。除此之外,我想不出我们想要使用的任何理由Thread.sleep()。