網站首頁 編程語言 正文
一、cancel()無效
當協程任務被取消的時候,它的內部是會產生一個 CancellationException 的。而協程的結構化并發,最大的優勢就在于:如果我們取消了父協程,子協程也會跟著被取消。
1.cancel()不被響應
val job = launch(Dispatchers.Default) {
var i = 0
while (true) {
Thread.sleep(500L)
i++
println("i =$i")
}
}
delay(2000L)
job.cancel()
job.join()
println("END")
}
Log
i =1
i =2
i =3
i =4
i =5
i =6
i =7
i =8
i =9
i =10
i =11
i =12
......
程序無法停止
上面的程序無法停止,協程任務的取消,需要互相協作。協程外部取消,協程內部需要做出響應才行。當我們調用 job.cancel() 以后,協程任務已經不是活躍狀態了,但代碼并沒有把 isActive 作為循環條件,因此協程無法真正取消。
可以在協程體中加入狀態判斷:
runBlocking {
val job = launch(Dispatchers.Default) {
var i = 0
while (isActive) {
Thread.sleep(500L)
i++
println("i =$i")
}
}
delay(2000L)
job.cancel()
job.join()
println("END")
}
Log
?
i =1
i =2
i =3
i =4
END
?
Process finished with exit code 0
把 while 循環的條件改成了 while (isActive),這就意味著,只有協程處于活躍狀態的時候,才會繼續執行循環體內部的代碼。協程的取消需要內部的配合。
2.結構被破壞
協程是結構化的,當我們取消父協程的時候,子協程也會跟著被取消。
但是特殊情況是嵌套創建的子協程并不會跟隨父協程一起取消。
runBlocking {
val parentJob = launch(fixedDispatcher) {
launch(Job()) {
var i = 0
while (isActive) {
Thread.sleep(500L)
i++
println("First i:$i")
}
}
launch {
var i = 0
while (isActive) {
Thread.sleep(500L)
i++
println("Second i:$i")
}
}
}
delay(2000L)
parentJob.cancel()
parentJob.join()
println("End")
}
Log
?
Second i:1
First i:1
First i:2
Second i:2
Second i:3
First i:3
Second i:4
First i:4
End
First i:5
First i:6
First i:7
First i:8
First i:9
First i:10
First i:11
First i:12
First i:13
First i:14
First i:15
......
可以發現,創建子協程的時候,使用了 launch(Job()){},就打破了原有的協程結構。因為 launch(Job()){}創建的協程的父 Job 是在 launch 當中傳入的 Job() 對象。所以調用 parentJob.cancel() 的時候,無法銷毀該協程。
可以按如下修改:
runBlocking {
val parentJob = launch(fixedDispatcher) {
launch {
var i = 0
while (isActive) {
Thread.sleep(500L)
i++
println("First i:$i")
}
}
launch {
var i = 0
while (isActive) {
Thread.sleep(500L)
i++
println("Second i:$i")
}
}
}
delay(2000L)
parentJob.cancel()
parentJob.join()
println("End")
}
First i:1
Second i:1
First i:2
Second i:2
First i:3
Second i:3
First i:4
Second i:4
End
parentJob 與它內部的子協程之間都是父子關系,因此它們兩個都是會響應協程取消的事件的。不要輕易打破協程的父子結構!
3.未正確處理 CancellationException
對于 Kotlin 提供的掛起函數,可以自動響應協程的取消。
例如:
runBlocking {
val parentJob = launch(Dispatchers.Default) {
launch {
var i = 0
while (true) {
delay(500L)
i++
println("First i = $i")
}
}
launch {
var i = 0
while (true) {
delay(500L)
i++
println("Second i = $i")
}
}
}
delay(2000L)
parentJob.cancel()
parentJob.join()
println("End")
}
First i = 1
Second i = 1
First i = 2
Second i = 2
First i = 3
Second i = 3
End
?
Process finished with exit code 0
delay() 函數可以自動檢測當前的協程是否已經被取消,如果已經被取消的話,它會拋出一個 CancellationException,從而終止當前的協程。
runBlocking {
val parentJob = launch(Dispatchers.Default) {
launch {
var i = 0
while (true) {
try {
delay(500L)
} catch (e: CancellationException) {
println("Catch CancellationException")
throw e
}
i++
println("First i =$i")
}
}
launch {
var i = 0
while (true) {
delay(500L)
i++
println("Second i = $i")
}
}
}
delay(2000L)
parentJob.cancel()
parentJob.join()
println("END")
}
Log:
?
First i =1
Second i = 1
First i =2
Second i = 2
First i =3
Second i = 3
Catch CancellationException
END
?
Process finished with exit code 0
try-catch 包裹了 delay() 以后,打印出“Catch CancellationException”,這就說明 delay() 確實可以自動響應協程的取消,并且產生 CancellationException 異常。
注意:捕獲了 CancellationException 以后沒有重新拋出去,就導致子協程無法正常取消。
runBlocking {
val parentJob = launch(Dispatchers.Default) {
launch {
var i = 0
while (true) {
try {
delay(500L)
} catch (e: CancellationException) {
println("Catch CancellationException")
//throw e
}
i++
println("First i =$i")
}
}
launch {
var i = 0
while (true) {
delay(500L)
i++
println("Second i = $i")
}
}
}
delay(2000L)
parentJob.cancel()
parentJob.join()
println("END")
}
......
First i =656179
Catch CancellationException
First i =656180
Catch CancellationException
First i =656181
Catch CancellationException
First i =656182
Catch CancellationException
First i =656183
Catch CancellationException
.....
所以,捕獲了 CancellationException 以后,要考慮是否應該重新拋出來。
二、try-catch不起作用
runBlocking {
try {
launch {
delay(100L)
1 / 0
}
} catch (e: Exception) {
println("catch: $e")
}
delay(500L)
println("End")
}
Log
Exception in thread "main" java.lang.ArithmeticException: / by zero
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt$testTryCatch8$1$1.invokeSuspend(TestTryCatch.kt:225)
?? ?at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
?? ?at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
?? ?at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:518)
?? ?at kotlinx.coroutines.EventLoopImplBase$DelayedResumeTask.run(EventLoop.common.kt:494)
?? ?at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
?? ?at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
?? ?at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
?? ?at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
?? ?at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
?? ?at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.testTryCatch8(TestTryCatch.kt:221)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.main(TestTryCatch.kt:15)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.main(TestTryCatch.kt)
Process finished with exit code 1
可以發現:try-catch 并沒有成功捕獲異常,程序等待了 100 毫秒左右,最終還是崩潰了。
使用async
runBlocking {
var deffered: Deferred<Any>? = null
try {
deffered = async {
delay(100L)
1 / 0
}
} catch (e: ArithmeticException) {
println("Catch:$e")
}
deffered?.await()
println("End")
}
Exception in thread "main" java.lang.ArithmeticException: / by zero
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt$testTryCatch9$1$1.invokeSuspend(TestTryCatch.kt:242)
?? ?at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
?? ?at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
?? ?at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:518)
?? ?at kotlinx.coroutines.EventLoopImplBase$DelayedResumeTask.run(EventLoop.common.kt:494)
?? ?at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
?? ?at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
?? ?at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
?? ?at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
?? ?at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
?? ?at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.testTryCatch9(TestTryCatch.kt:237)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.main(TestTryCatch.kt:16)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.main(TestTryCatch.kt)
Process finished with exit code 1
當協程體當中的“1/0”執行的時候,程序已經跳出 try-catch 的作用域了,所以 try-catch失效。
把 try-catch 挪到 launch{} 協程體內部。可以正常捕獲到 ArithmeticException 這個異常了。
runBlocking {
var deffered: Deferred<Any>? = null
deffered = async {
try {
delay(100L)
1 / 0
} catch (e: ArithmeticException) {
println("Catch:$e")
}
}
deffered.await()
println("End")
}
Log
?
Catch:java.lang.ArithmeticException: / by zero
End
?
Process finished with exit code 0
注意:不要用 try-catch 直接包裹 launch、async。
使用 try-catch 包裹“deferred.await()”。
例:
runBlocking {
var deffered = async {
delay(100L)
1 / 0
}
try {
deffered.await()
} catch (e: Exception) {
println("Catch:$e")
}
println("End")
}
atch:java.lang.ArithmeticException: / by zero
End
Exception in thread "main" java.lang.ArithmeticException: / by zero
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt$testTryCatch11$1$deffered$1.invokeSuspend(TestTryCatch.kt:275)
?? ?at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
?? ?at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
?? ?at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:518)
?? ?at kotlinx.coroutines.EventLoopImplBase$DelayedResumeTask.run(EventLoop.common.kt:494)
?? ?at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
?? ?at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
?? ?at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
?? ?at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
?? ?at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
?? ?at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.testTryCatch11(TestTryCatch.kt:272)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.main(TestTryCatch.kt:16)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.main(TestTryCatch.kt)
?
Process finished with exit code 1
await() 如果不調用的話,async 當中的異常是否發生?
runBlocking {
var deffered = async {
delay(100L)
1 / 0
}
delay(500L)
println("End")
}
Exception in thread "main" java.lang.ArithmeticException: / by zero
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt$testTryCatch12$1$deffered$1.invokeSuspend(TestTryCatch.kt:290)
?? ?at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
?? ?at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
?? ?at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:518)
?? ?at kotlinx.coroutines.EventLoopImplBase$DelayedResumeTask.run(EventLoop.common.kt:494)
?? ?at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
?? ?at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
?? ?at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
?? ?at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
?? ?at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
?? ?at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.testTryCatch12(TestTryCatch.kt:287)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.main(TestTryCatch.kt:16)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.main(TestTryCatch.kt)
?
Process finished with exit code 1
可見,async 當中產生異常,即使不調用 await() 同樣是會導致程序崩潰的。
三、SupervisorJob
使用 try-catch 包裹“deferred.await()”,需要配合 SupervisorJob 一起使用。實現“不調用 await() 就不會產生異常而崩潰”。
unBlocking {
val scope = CoroutineScope(SupervisorJob())
scope.async {
delay(100L)
1 / 0
}
delay(500L)
println("End")
}
Log
?
End
?
Process finished with exit code 0
使用 SupervisorJob 創建一個 scope 以后,用 scope.async{}啟動協程后,只要不調用“deferred.await()”,程序就不會因為異常而崩潰。
runBlocking {
val coroutineScope = CoroutineScope(SupervisorJob())
val deferred = coroutineScope.async {
delay(100L)
1 / 0
}
try {
deferred.await()
} catch (e: Exception) {
println("Catch:$e")
}
delay(500L)
println("End")
}
Log
Catch:java.lang.ArithmeticException: / by zero
End
?
Process finished with exit code 0
使用“coroutineScope.async {}”創建了協程,同時也用 try-catch 包裹“deferred.await()”,這樣一來,異常就成功地被捕獲了。
public fun SupervisorJob(parent: Job? = null) : CompletableJob
= SupervisorJobImpl(parent)
public interface CompletableJob : Job {
public fun complete(): Boolean
public fun completeExceptionally(exception: Throwable): Boolean
}
SupervisorJob() 不是構造函數,它只是一個普通的頂層函數。這個方法返回的對象,是 Job 的子類。SupervisorJob 與 Job 最大的區別就在于,當它的子 Job 發生異常的時候,其他的子 Job 不會受到牽連。
對于普通 Job, 出現異常時的應對策略是:由于 parentJob 是一個普通的 Job 對象,當 job1 發生異常之后,它會導致 parentJob 取消,進而導致 job2、job3 也受到牽連。
如果把 parentJob 改為 SupervisorJob,job1 發生異常的的話,就不會影響到其他的 Job 了。
注意:靈活使用 SupervisorJob,控制異常傳播的范圍。
四、CoroutineExceptionHandler
runBlocking {
val coroutineScope = CoroutineScope(coroutineContext)
coroutineScope.launch {
async {
delay(100L)
}
launch {
delay(100L)
1/0
}
}
delay(1000L)
println("END")
}
Exception in thread "main" java.lang.ArithmeticException: / by zero
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt$testTryCatch15$1$1$2.invokeSuspend(TestTryCatch.kt:338)
?? ?at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
?? ?at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
?? ?at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:518)
?? ?at kotlinx.coroutines.EventLoopImplBase$DelayedResumeTask.run(EventLoop.common.kt:494)
?? ?at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
?? ?at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
?? ?at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
?? ?at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
?? ?at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
?? ?at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.testTryCatch15(TestTryCatch.kt:329)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.main(TestTryCatch.kt:16)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.main(TestTryCatch.kt)
?
Process finished with exit code 1
使用CoroutineExceptionHandler處理上述代碼中的異常。
runBlocking {
val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
println("Catch: $throwable")
}
val coroutineScope = CoroutineScope(coroutineContext + Job() + coroutineExceptionHandler)
coroutineScope.launch {
async {
delay(100L)
}
launch {
delay(100L)
1 / 0
}
}
delay(1000L)
println("END")
}
Log
Catch: java.lang.ArithmeticException: / by zero
END
?
Process finished with exit code 0
定義了一個 CoroutineExceptionHandler,然后把它傳入了 scope 當中,就可以捕獲其中所有的異常了。
注意點:在特定場景,為什么 CoroutineExceptionHandler 不起作用?
runBlocking {
val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
println("Catch: $throwable")
}
val coroutineScope = CoroutineScope(coroutineContext)
coroutineScope.launch {
async {
delay(100L)
}
launch(coroutineExceptionHandler) {
delay(100L)
1 / 0
}
}
delay(1000L)
println("END")
}
Exception in thread "main" java.lang.ArithmeticException: / by zero
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt$testTryCatch17$1$1$2.invokeSuspend(TestTryCatch.kt:383)
?? ?at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
?? ?at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
?? ?at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
?? ?at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:518)
?? ?at kotlinx.coroutines.EventLoopImplBase$DelayedResumeTask.run(EventLoop.common.kt:494)
?? ?at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
?? ?at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
?? ?at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
?? ?at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
?? ?at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
?? ?at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.testTryCatch17(TestTryCatch.kt:371)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.main(TestTryCatch.kt:16)
?? ?at com.example.myapplication.testcoroutinue.TestTryCatchKt.main(TestTryCatch.kt)
?
Process finished with exit code 1
把自定義的 myExceptionHandler,放到出現異常的 launch 那里傳了進去。myExceptionHandler 并不會起作用,異常不會被它捕獲。注意:myExceptionHandler 直接定義在發生異常的位置反而不生效,而定義在最頂層卻可以生效。因為只在頂層的協程當中才會起作用。也就是說,當子協程當中出現異常以后,它們都會統一上報給頂層的父協程,然后頂層的父協程才會去調用 CoroutineExceptionHandler,來處理對應的異常。所以需要記住:使用 CoroutineExceptionHandler 處理復雜結構的協程異常,它僅在頂層協程中起作用。
原文鏈接:https://blog.csdn.net/zhangying1994/article/details/127610104
相關推薦
- 2022-05-03 EF使用Code?First模式生成單數形式表名_實用技巧
- 2022-06-16 Python實現視頻下載與合成的示例代碼_python
- 2022-07-06 C++使用easyx實現打磚塊游戲_C 語言
- 2022-03-25 Unity實現圓形Image組件_C#教程
- 2022-10-25 C++構建函數使用介紹_C 語言
- 2022-04-26 python計算寄送包裹重量的實現過程_python
- 2022-09-01 C語言中static與sizeof查缺補漏篇_C 語言
- 2022-04-29 WPF自定義路由事件_實用技巧
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支