<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                ## 協程取消 ### 協程取消 在項目開發的過程中,當進入一個需要網絡請求的界面中時,在該界面請求2秒,用戶沒有看到界面加載的數據就關閉了當前的界面,此時對應的網絡請求任務就需要關閉掉,這個網絡請求的線程也需要關閉。 同樣的道理,在協程程序中,如果開啟了一個協程來進行網絡請求或者數據加載,當退出該界面時,該界面的數據還未加載完成,此時就需要取消協程。在Kotlin中是通過cancel()方法將協程取消的。接下來我們通過一個案例來演示如何取消協程,具體代碼如下所示。 ``` import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.runBlocking fun main(args: Array<String>): Unit = runBlocking { val job = launch { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } } delay(2000L) println("協程取消前:isActive=${job.isActive} isCompleted=${job.isCompleted}") job.cancel() //取消協程 println("協程取消后:isActive=${job.isActive} isCompleted=${job.isCompleted}") } ``` 運行結果: ``` I'm sleeping 0… I'm sleeping 1… I'm sleeping 2… I'm sleeping 3… 協程取消前:isActive=true isCompleted=false 協程取消后:isActive=false isCompleted=true ``` 上述代碼中,第7行的repeat()方法表示的是重復1000次來打印“I'm sleeping$i…”,在第15行通過job.cancel()來取消協程,在取消協程的前后分別打印了job中任務的狀態,根據該程序的運行結果可知,協程在取消之前isActive的值為true,isCompleted的值為false,表示該協程在活動中。由于在協程取消時,會出現兩種情況,一種是正在取消,此時打印出的isActive的值為false,isCompleted的值為false;另一種是已經取消,此時打印出的isActive的值為false,isCompleted的值為true。這兩種情況都表示協程取消成功。 >[info] **注意** 上述程序的運行結果中,協程取消后的信息有兩種情況,具體如下。 第1種,正在取消協程時,運行結果為: 協程取消后:`isActive=false isCompleted=false ` 第2種,已經取消協程時,運行結果為: 協程取消后:`isActive=false isCompleted=true ` #### **多學一招**:cancelAndJoin()函數與不可取消代碼塊 1. cancelAndJoin()函數與finally代碼塊 協程中的cancel()函數和join()函數是可以進行合并的,合并之后是一個cancelAndJoin()函數,這個函數用于取消協程。接下來我們通過一個案例來演示cancelAndJoin()函數取消協程以及在協程中使用try…finally代碼塊。具體代碼如下所示。 ``` import kotlinx.coroutines.experimental.cancelAndJoin import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.runBlocking fun main(args: Array<String>): Unit = runBlocking { val job = launch { try { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } } finally { println("之前最終執行的代碼") delay(1000L) println("之后最終執行的代碼") } } delay(2000L) job.cancelAndJoin()//取消協程 } ``` 運行結果: ``` I'm sleeping 0… I'm sleeping 1… I'm sleeping 2… I'm sleeping 3… ``` 之前最終執行的代碼 根據上述代碼的運行結果可知,沒有打印第16行代碼需要打印的數據,這是由于當程序輸出“I'm sleeping 3…”時,當前程序耗時是1500ms,主線程的延遲時間是2000ms,此時程序會繼續執行finally中的代碼。當執行完第14行代碼時,程序需要延遲的時間為1000ms,此時主線程的延遲時間已經到了,主線程會繼續運行第20行代碼取消協程,由于協程結束時,守護線程也就結束,因此finally中的代碼不會繼續執行。 2. 不可取消代碼塊 如果想讓【文件9-14】中的程序不受協程結束的影響,繼續執行finally中的代碼,則需要在finally中通過withContext{}代碼塊來實現,這個代碼塊稱為不可取消的代碼塊,具體代碼如下所示: ``` // 不可取消的代碼塊 withContext(NonCancellable){ println(" 之前最終執行的代碼") delay(1000L) println(" 之后最終執行的代碼") } ``` ### 協程取消失效 一般情況下,一個協程需要通過cancel()方法來取消,這種取消方式只適用于在協程代碼中有掛起函數的程序。由于掛起函數在掛起時也就是等待時,該協程已經回到了線程池中,等待時間結束之后會重新從線程池中恢復出來,雖然可以通過cancel()方法取消這些掛起函數,但是在協程中調用某些循環輸出數據的函數時,通過cancel()方法是取消不了這個協程的。接下來我們通過一個案例來演示通過cancel()方法無法取消的協程,具體代碼如下所示。 ~~~ import kotlinx.coroutines.experimental.* fun main(args: Array<String>): Unit = runBlocking { val job = launch(CommonPool) { //程序運行時當前的時間 var nextTime = System.currentTimeMillis() while (true) { /* if(!isActive) return@launch //返回當前協程*/ try { yield() }catch (e:CancellationException){ println("異常名稱=${e.message}") return@launch } //每一次循環的時間 var currentTime = System.currentTimeMillis() if (currentTime > nextTime) { println("當前時間:${System.currentTimeMillis()}") nextTime += 1000L } } } delay(2000L) //使程序延遲2秒鐘 println("協程取消前:isActive=${job.isActive}") job.cancel() //取消協程 job.join() println("協程取消后:isActive=${job.isActive}") } ~~~ 運行結果: ``` 當前時間:1531983528698 當前時間:1531983529698 當前時間:1531983530698 協程取消前:isActive=true 當前時間:1531983531698 當前時間:1531983532698 …… ``` 上述協程代碼中,通過while循環每隔1000ms打印一次當前時間,如果通過cancel()方法來取消這個協程時,會發現該協程并沒有停止,一直處于存活狀態,并無限循環地打印數據,因此第23行代碼中協程取消后的狀態就不能打印了。如果協程的循環代碼中沒有掛起函數,則該程序是不能直接通過cancel()方法來取消的。 有一些協程中有循環代碼且沒有掛起函數的程序,如果想取消協程,則需要對這個協程中的Job任務狀態進行判斷。如果協程取消失效后,則可以通過以下兩種方案來繼續取消協程。 方案一:通過對isActive值的判斷來取消協程}/pa 如果想要在結束協程時結束協程中的循環操作,則需要在循環代碼中通過isActive的值來判斷當前協程的狀態,如果isActive的值為false,則表示當前協程處于結束狀態,此時返回當前協程即可,具體代碼如下所示: ``` //判斷當前協程狀態 if(!isActive) return@launch //返回當前協程 ``` 上述代碼需要添加在【文件9-15】中的第11行代碼上方。此時運行該文件中的程序,運行結果如下所示。 ``` 當前時間:1531984991424 當前時間:1531984992423 當前時間:1531984993423 協程取消前:isActive=true 協程取消后:isActive=false ``` 方案二:使用yield()掛起函數來取消協程 除了上述解決方案之外,還可以在循環代碼中調用yield()掛起函數來結束協程中的循環操作,因為調用cancel()函數來結束協程時,yield()會拋出一個異常,這個異常的名稱是Cancellation Exception,拋出這個異常之后協程中的循環操作就結束了,同時在循環代碼中通過try…catch來捕獲這個異常并打印異常名稱,當捕獲到這個異常之后將協程返回即可。具體代碼如下: ``` try { yield() }catch (e:CancellationException){ println(" 異常名稱=${e.message}") return@launch } ``` 上述代碼需要添加在【文件9-15】中的第11行代碼上方。此時運行該文件中的程序,運行結果如下所示。 ``` 當前時間:1531985095581 當前時間:1531985096581 當前時間:1531985097581 協程取消前:isActive=true 異常名稱=Job was cancelled normally 協程取消后:isActive=false ``` ### 定時取消 一般情況下,在掛起函數delay()中傳遞的時間到了之后會通過cancel()方法來取消協程,例如當打開一個應用的界面時,此時程序需要發送網絡請求來獲取界面中的數據,如果網絡很慢、沒有網絡或者服務器有問題,請求3、4秒還沒有請求到數據,則用戶可能會沒有耐心而將該界面關閉,此時后臺請求的任務就斷掉了,這樣的用戶體驗很差。通常我們會給網絡請求設置一個超時的時間,對于協程來說也是一樣的,對于后臺的耗時任務一般是需要設置一個時間的上限,時間到了之后就可以將這個協程取消。在協程中可以通過withTimeout()函數來限制取消協程的時間。接下來我們通過一個案例來演示如何通過withTimeout()函數在限制時間內取消協程,具體代碼如【文件9-16】所示。 ``` import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.runBlocking import kotlinx.coroutines.experimental.withTimeout fun main(args: Array<String>): Unit = runBlocking { val job = launch { withTimeout(2000L) { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } } } job.join() } ``` 運行結果: ``` I'm sleeping 0… I'm sleeping 1… I'm sleeping 2… I'm sleeping 3… ``` 上述代碼中,通過withTimeout()函數來設置超過指定時間后協程會自動取消,withTimeout()函數中傳遞的2000L表示2秒。根據程序中的邏輯代碼可知,每打印一行數據程序都會延遲500ms,打印4行數據后,程序的延遲時間一共為2000ms,等到下一次打印數據時,已經超過了協程的限制時間2秒,此時協程會自動取消,不再繼續打印數據。 ### 掛起函數的執行順序 如果想要在協程中按照順序執行程序中的代碼,則只需要使用正常的順序來調用即可,因為協程中的代碼與常規的代碼一樣,默認情況下是按照順序執行的。接下來我們通過執行兩個掛起函數來演示協程中程序默認執行的順序,具體代碼如下所示。 ``` import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.experimental.runBlocking import kotlin.system.measureTimeMillis fun callMethod(): Unit = runBlocking { val time = measureTimeMillis { val a = doJob1() val b = doJob2() println("a=$a b=$b") } println("執行時間=$time") } suspend fun doJob1(): Int { //掛起函數doJob1() println("do job1") delay(1000L) println("job1 done") return 1 } suspend fun doJob2(): Int { //掛起函數doJob2() println("do job2") delay(1000L) println("job2 done") return 2 } fun main(args: Array<String>) { callMethod() } ``` 運行結果: ``` do job1 job1 done do job2 job2 done a=1 b=2 執行時間=2051 ``` 上述代碼中,創建了兩個掛起函數,分別是doJob1()和doJob2(),運行每個函數時都通過delay()函數使程序延遲了1秒。在callMethod()方法中,通過measureTimeMillis()函數來獲取程序運行兩個掛起函數所耗費的時間。根據程序的運行結果可知,兩個掛起函數的執行與其在程序中的調用順序是一致的,運行兩個掛起函數耗費的時間是2051,由此可以看出同步執行程序是比較耗時的。 ### 通過async啟動協程 上一小節的DoJob.kt文件中的代碼是同步執行的,這樣執行比較耗時。為了使程序執行不耗費很多時間,可以使用異步任務來執行程序。從概念上講,異步任務就如同啟動一個單獨的協程,它是一個與其他所有協程同時工作的輕量級線程。前幾節中的協程就屬于一個異步任務,除了通過launch()函數來啟動協程之外,還可以通過async()函數來啟動協程,不同之處在于launch()函數返回的是一個Job任務并且不帶任何結果值,而async()函數返回的是一個Deferred(也是Job任務,可以進行取消),這是一個輕量級的非阻塞線程,它有返回結果,可以使用.await()延期值來獲取。接下來我們通過異步代碼來執行上面的代碼,修改后的代碼如下所示。 ``` import kotlinx.coroutines.experimental.async import kotlinx.coroutines.experimental.runBlocking import kotlin.system.measureTimeMillis fun asyncCallMethod(): Unit = runBlocking { val time = measureTimeMillis { val a = async { doJob1() } //通過async函數啟動協程 val b = async { doJob2() } println("a=${a.await()} b=${b.await()}") } println("執行時間=$time") } fun main(args: Array<String>) { asyncCallMethod() } ``` 運行結果: ``` do job1 do job2 job2 done job1 done a=1 b=2 執行時間=1045 ``` 上述代碼中的函數doJob1()與doJob2()是前面示例中創建的,在此處不重復寫一遍了,在程序中通過await()函數分別獲取函數doJob1()與doJob2()的返回值。根據程序的運行結果可知,通過async()函數異步啟動協程,程序的運行順序不是默認的順序,是隨機的,并且根據程序的執行時間與前面示例中程序的執行時間對比可知,異步運行協程比同步運行要節省較多時間。 一般情況下,通過launch()函數啟動沒有返回值的協程,通過async()函數啟動有返回值的協程。 ### 協程上下文和調度器 在Kotlin中,協程的上下文使用CoroutineContext表示,協程上下文是由一組不同的元素組成,其中主要元素是前面學到的協程的Job與本小節要學習的調度器。協程上下文中包括協程調度程序(又稱協程調度器),協程調度器可以將協程執行限制在一個特定的線程中,也可以給它分派一個線程池或者可以不做任何限制無約束地運行。所有協程調度器都接收可選的CoroutineContext參數,該參數可用于為新協程和其他上下文元素顯示指定調度器。接下來我們通過一個案例來演示協程的上下文和調度器,具體代碼如下所示。 ``` import kotlinx.coroutines.experimental.* fun main(args: Array<String>): Unit = runBlocking { val list = ArrayList<Job>() list += launch(Unconfined) { //主協程的上下文 println("Unconfined執行的線程=${Thread.currentThread().name}") } list += launch(coroutineContext) { //使用的是父協程的上下文 println("coroutineContext執行的線程=${Thread.currentThread().name}") } list += launch(CommonPool) { //線程池中的線程 println("CommonPool執行的線程=${Thread.currentThread().name}") } list += launch(newSingleThreadContext("new thread")) { //運行在新線程中 println("新線程執行的線程=${Thread.currentThread().name}") } list.forEach{ it.join() } } ``` 運行結果: ``` Unconfined執行的線程=main coroutineContext執行的線程=main 新線程執行的線程=new thread CommonPool執行的線程=ForkJoinPool.commonPool-worker-1 ``` 根據該程序的運行結果可知,啟動協程時,launch()函數中傳遞Unconfined主協程上下文時,程序執行的是主線程,傳遞coroutineContext父協程上下文時,程序執行的也是主線程,傳遞CommonPool線程池時,程序執行的是某一個線程,傳遞newSingleThreadContext("new thread")新線程時,程序執行的是新線程new thread。 >[info] 注意 上述程序中,由于執行的是4個協程,而協程是一種輕量級線程,多線程的執行順序是不固定的,因此上述程序執行的先后順序是不固定的。 ### 父子協程 當使用coroutineContext(協程上下文)來啟動另一個協程時,新協程的Job就變成父協程工作的一個子任務,當父協程被取消時,它的所有子協程也被遞歸地取消。接下來我們通過一個案例來演示取消父協程時,與其對應的子協程也會被取消,具體代碼如下所示。 ``` import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.runBlocking fun main(args: Array<String>) :Unit= runBlocking{ val request = launch { //父協程 val job1 = launch { println("啟動協程1") delay(1000L) println("協程1執行完成") } //子協程,使用的上下文是request對應的協程上下文 val job2 = launch (coroutineContext){ println("啟動協程2") delay(1000L) println("協程2執行完成") } } delay(500L) request.cancel() delay(2000L) } ``` 運行結果: ``` 啟動協程1 啟動協程2 協程1執行完成 ``` 上述代碼中,首先通過launch()函數啟動了一個request協程,接著通過coroutineContext協程上下文啟動了一個子協程job2,主線程中通過delay()函數一共延遲了2500ms,而開啟的兩個協程通過delay()函數一共延遲了2000ms,根據程序的運行結果可知,當通過cancel()方法取消主協程request時,子協程job2也自動取消了,因此運行結果沒有打印“協程2執行完成”。 >[info] 注意 由于第20行代碼中的cancel()方法的返回值是boolean類型,而main()函數不需要返回值,因此在這行代碼下方任意輸出一段字符串即可,不然程序會報錯。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看