一學(xué)就會的協(xié)程使用——基礎(chǔ)篇(九)異常與supervisor

1. 引言

僅知道協(xié)程中可以用CoroutineExceptionHandler來捕獲區(qū)里異常避免閃退,是遠(yuǎn)遠(yuǎn)不夠的虏辫,因為協(xié)程中的異常傳遞與處理部分认然,與協(xié)程結(jié)構(gòu)化并發(fā)部分息息相關(guān)妓布,一不小心堡掏,非常容易踩到坑上!

2. 異常干擾其它協(xié)程

在協(xié)程作用域的介紹使用當(dāng)中吕座,創(chuàng)建協(xié)程作用域的方式是:

private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

為什么這個代碼要顯示構(gòu)造SupervisorJob()虐译?這是個好問題,與本文內(nèi)容息息相關(guān)吴趴。

因為顯式構(gòu)造這個的功能漆诽,只有在多協(xié)程并產(chǎn)生協(xié)程結(jié)構(gòu)化的時候,才會表現(xiàn)出來锣枝。

先再多創(chuàng)建一個協(xié)程作用域厢拭,方便與前面的作用域?qū)ο笞鲗Ρ龋?/p>

private val jobScope = CoroutineScope(Job() + Dispatchers.Main)

然后,接下來撇叁,為方面對比實(shí)際效果供鸠,封裝下面的函數(shù):

private fun launchTenSecondsCoroutine(scope: CoroutineScope, extraMsg: String) {
    scope.launch(Dispatchers.IO) {
        val targetMilli = System.currentTimeMillis() + TEN_SECONDS
        while (true) {
            ensureActive()
            if (System.currentTimeMillis() > targetMilli) {
                break
            }
            myLog("launchLoopingCoroutine $extraMsg")
            Thread.sleep(ONE_SECOND)
        }
    }
}

很簡單,就是用函數(shù)參數(shù)傳遞進(jìn)來的協(xié)程作用域啟動一個IO協(xié)程陨闹,這個協(xié)程會在10秒中之內(nèi)不斷循環(huán)楞捂,同時每次循環(huán)開啟前提供一個協(xié)程取消協(xié)作點(diǎn)薄坏,使得協(xié)程可以被取消。

再封裝一個啟動協(xié)程一個協(xié)程并在執(zhí)行的5秒后會拋出異常的函數(shù):

private fun launchCoroutineThrowException(scope: CoroutineScope, extraMsg: String) {
    scope.launch(exceptionHandler + Dispatchers.IO) {
        Thread.sleep(FIVE_SECONDS)
        throw IllegalStateException("$extraMsg Throw exception!")
    }
}

這里啟動協(xié)程前傳入了協(xié)程異常處理者對象:

private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
    myLog("CoroutineExceptionHandler:$throwable")
}

捕獲到異常時僅打印log信息寨闹。

現(xiàn)在胶坠,到了真正的實(shí)踐代碼環(huán)節(jié),通過前面jobScope對象來啟動3個協(xié)程繁堡,即兩個僅執(zhí)行10秒的協(xié)程和一個執(zhí)行5秒后會拋出異常且?guī)в袇f(xié)程異常處理者的協(xié)程:

private fun launchWithJobClicked() {
    myLog("launchWithJobClicked")
    launchTenSecondsCoroutine(jobScope, "launchWithJobClicked A")
    launchTenSecondsCoroutine(jobScope, "launchWithJobClicked B")
    launchCoroutineThrowException(jobScope, "launchWithJobClicked")
}

小小賣個關(guān)子沈善,這里不妨先想一下:應(yīng)用會因為異常的拋出而閃退嗎?如果不會椭蹄,那么協(xié)程A和協(xié)程B矮瘟,實(shí)際的執(zhí)行時間大概有多長?

最終的結(jié)果會是:

應(yīng)用不會因為協(xié)程的拋出而閃退塑娇,在5秒時有一個異常觸發(fā)CoroutineExceptionHandler的處理,但協(xié)程A和協(xié)程B劫侧,實(shí)際的執(zhí)行時間只有5秒左右(根據(jù)log的輸出情況判斷),永遠(yuǎn)達(dá)不到10秒烧栋。

是不是很詭異?明明協(xié)程A跟B的執(zhí)行邏輯审姓,如果協(xié)程沒有被取消的話,明明會執(zhí)行10秒的魔吐!為什么這里的log在5秒后就不再輸出了呢?

等等酬姆,注意前面這句話嗜桌,“如果協(xié)程沒有被取消的話”,從最終的執(zhí)行結(jié)果反過來想辞色,會不會是因為協(xié)程被取消了呢骨宠?但是這時候又沒有點(diǎn)擊取消按鈕啊,為什么協(xié)程被取消了呢相满?

這便是一個協(xié)程異常處理者的一個大坑层亿,當(dāng)一個協(xié)程中遇到的異常用CoroutineExceptionHandler處理以后,默認(rèn)情況下立美,當(dāng)前協(xié)程會將遇到的異常繼續(xù)向父協(xié)程中傳遞并取消父協(xié)程匿又,而父協(xié)程的取消必然會取消其所有子協(xié)程

回到實(shí)踐代碼上悯辙,由于launchCoroutineThrowException中啟動的協(xié)程和協(xié)程A/B三者之間會為兄弟協(xié)程琳省,三者有個共同的父Job迎吵,即為jobScope對象在構(gòu)造時所創(chuàng)建Job對象。

當(dāng)launchCoroutineThrowException的協(xié)程中拋出了異常且被CoroutineExceptionHandler處理后针贬,會進(jìn)一步地取消父Job击费,而父Job的取消,從而使得其另外的兩個子協(xié)程A和B被取消桦他。

所以蔫巩,這便解析了,為什么協(xié)程A和協(xié)程B在執(zhí)行過程中只執(zhí)行5秒左右快压。

這里還需要注意圆仔,在launchWithJobClicked在頁面第一次被調(diào)用的時候,啟動的三個協(xié)程均得到執(zhí)行蔫劣,只不過是5秒后會結(jié)束坪郭,而在這個函數(shù)第二次及以后執(zhí)行中,所啟動的協(xié)程函數(shù)體中的內(nèi)容將不再獲得執(zhí)行脉幢,不妨再想想歪沃,為什么?此非本文重點(diǎn)嫌松,但這個對協(xié)程結(jié)構(gòu)化并發(fā)的理解其實(shí)也很重要沪曙。

3. 異常不干擾其它協(xié)程

因為某一個協(xié)程出現(xiàn)異常導(dǎo)致其父協(xié)程以及其他的兄弟協(xié)程的取消,這種場景肯定是有的萎羔,比如同時發(fā)出一系列請求贾陷,有一個請求出現(xiàn)異常而失敗時將導(dǎo)致最終請求結(jié)果為失敗髓废,這時候出現(xiàn)異常后及時取消其他協(xié)程,是合理的砸喻。

但是割岛,如果希望協(xié)程間互相獨(dú)立呢癣漆?即某一個協(xié)程因異常而結(jié)束惠爽,并不希望其影響其他協(xié)程,因為同一個協(xié)程作用域啟動的協(xié)程租副,可能是互相影響結(jié)果用僧,也可能是互相獨(dú)立互不影響的部分责循。

這時候攀操,便是SupervisorJob()發(fā)揮作用的時候了意蛀。

前面啟動的協(xié)程是通過jobScope:

private val jobScope = CoroutineScope(Job() + Dispatchers.Main)

現(xiàn)在將換用scope對象:

private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

啟動協(xié)程的內(nèi)容和第2節(jié)中相同,只不過傳入的協(xié)程作用域?qū)ο髲?code>jobScope換成了scope

private fun launchWithSupervisorJobClicked() {
    launchTenSecondsCoroutine(scope, "launchWithSupervisorJobClicked A")
    launchTenSecondsCoroutine(scope, "launchWithSupervisorJobClicked B")
    launchCoroutineThrowException(scope, "launchWithSupervisorJobClicked")
}

這時候秀姐,log的輸出結(jié)果听哭,是5秒時拋出一個異常被CoroutineExceptionHandler處理,而A/B兩個協(xié)程的log輸出在10秒內(nèi)始終輸出舷蟀,而且多次執(zhí)行launchWithSupervisorJobClicked野宜,里面啟動的協(xié)程函數(shù)體部分始終會獲得執(zhí)行河胎。

這里便是SupervisorJob()Job()兩種寫法導(dǎo)致的差異了虎敦,在某個協(xié)程拋出異常并被CoroutineExceptionHandler處理時,當(dāng)前協(xié)程會不會取消其父協(xié)程喷户。

嗯摩骨?前面好像說這個協(xié)程也會取消其兄弟協(xié)程恼五?為什么這里不寫后半句了灾馒?首先睬罗,再回第2節(jié)看看表述容达,并沒有協(xié)程拋出異常后會取消兄弟協(xié)程一說花盐,在第2節(jié)中的情況菇爪,兄弟協(xié)程的取消不是因為當(dāng)前協(xié)程遇到異常凳宙,而是因為父協(xié)程的取消必然會取消子協(xié)程氏涩,這部分是結(jié)構(gòu)化并發(fā)方面對于協(xié)程取消的設(shè)計奖亚,當(dāng)前協(xié)程不會取消父協(xié)程了析砸,所以兄弟協(xié)程自然就不會被取消作郭。。

SupervisorJob()Job()的區(qū)別蜘醋,僅僅只是,產(chǎn)生的Job對象會不會因子協(xié)程的異常而被取消胎食。

4. 創(chuàng)建協(xié)程作用域的考慮

通過第2節(jié)和第3節(jié)的對比厕怜,可以看出粥航,同一個協(xié)程作用域所啟動的協(xié)程之間是否會因異常而互相干擾递雀,關(guān)鍵是創(chuàng)建協(xié)程作用域之時所創(chuàng)建的Job元素對象是哪一種缀程。

如果希望其中一個協(xié)程產(chǎn)生異常而自動取消其他協(xié)程并后續(xù)不再能啟動協(xié)程,那么應(yīng)該用Job()秕衙;如果不希望某個協(xié)程產(chǎn)生異常時會影響到其他協(xié)程并后續(xù)可以繼續(xù)啟動協(xié)程,那么應(yīng)該用SupervisorJob()搞糕。

在Android平臺頁面的設(shè)計上窍仰,更多情況下應(yīng)該是SupervisorJob(),同時Android中提供的lifeCycleScopeviewModelScope兩種實(shí)現(xiàn)中所用的均是SupervisorJob()晶伦。

這里必要再提及一句,當(dāng)用CoroutineScope(context: CoroutineContext)這種方式創(chuàng)建協(xié)程作用域時泌参,如果不顯式提供Job元素的對象沽一,那么最終協(xié)程作用域中的Job元素對象類型將會是Job()。結(jié)合前面第二節(jié)的實(shí)踐代碼結(jié)果攘残,請謹(jǐn)慎在創(chuàng)建協(xié)程作用域時不提供Job元素的方式歼郭,比如謹(jǐn)慎使用CoroutineScope(Dispatchers.Main)這種寫法創(chuàng)建作用域?qū)ο蟛≡堑?節(jié)的是所實(shí)踐結(jié)果是業(yè)務(wù)需要的泰涂。

5. 結(jié)構(gòu)化并發(fā)中關(guān)于異常處理

在初識結(jié)構(gòu)化一文當(dāng)中,在“進(jìn)階版等待多協(xié)程完成”一節(jié)中寄疏,介紹的掛起函數(shù)是supervisorScope是牢,注意看這個函數(shù)的命名,是否與前面介紹的兩種Job之間的差異單詞陕截,是否很類似驳棱?是的,并不是不知道有coroutineScope這個农曲,也知道不少技術(shù)文章中均有使用這個coroutineScope這個函數(shù)作為結(jié)構(gòu)化并發(fā)介紹的社搅,前文中是故意地使用supervisorScope,避免后續(xù)觸及使用到CoroutineExceptionHandler時踩到隱藏的坑。

其實(shí)罚渐,在初始結(jié)構(gòu)化一文中的實(shí)踐代碼却汉,用到的supervisorScope的地方,換用coroutineScope也會是同樣的結(jié)果,而這兩者之間的區(qū)別,只有在啟動的協(xié)程中使用CoroutineExceptionHandler且遇上異常拋出時才會體現(xiàn)出來。

先說明一下,coroutineScope也是個掛起函數(shù)桨吊,而前面創(chuàng)建協(xié)程作用域用的方法CoroutineScope(CoroutineContext)是個普通頂層函數(shù)(并不是構(gòu)造函數(shù))留美,注意首字母的大小寫以及后面的參數(shù)區(qū)別以及是否掛起函數(shù)的區(qū)別奕枝。

說得有點(diǎn)多了忘晤,還是上代碼吧闰蛔。

private fun suspendWithJobClicked() {
    myLog("suspendWithJobClicked")
    scope.launch(exceptionHandler) {
        myLog("suspendWithJobClicked parent coroutine")
        val ret = coroutineScope {
            launchTenSecondsCoroutine(this, "suspendWithJobClicked A")
            launchTenSecondsCoroutine(this, "suspendWithJobClicked B")
            launchCoroutineThrowException(this, "suspendWithJobClicked")
            "coroutineScope final line"
        }
        myLog("suspendWithJobClicked parent coroutine final line: $ret")
    }
}

這里用掛起函數(shù)coroutineScope掛起當(dāng)前協(xié)程裁着,并且用產(chǎn)生的子協(xié)程作用域啟動兩個執(zhí)行10秒的協(xié)程和一個5秒后拋出異常的協(xié)程诸蚕,并且將在coroutineScope的lambda表達(dá)式最后一個表達(dá)式中返回字符串"coroutineScope final line"漠魏,在coroutineScope后輸出log壤巷,log中帶有coroutineScope的返回值有巧。

問:這里的執(zhí)行結(jié)果能打印出來"suspendWithJobClicked parent coroutine final line: xxx"這一行l(wèi)og嗎?如果能,log輸出的時間點(diǎn)是在協(xié)程啟動后的不久后命锄,還是5秒或10秒左右還是其他情況苟翻?最終的這一行l(wèi)og的拼接后的完整字符串內(nèi)容會是什么?

答:這里的執(zhí)行結(jié)果打印不出來"suspendWithJobClicked parent coroutine final line: xxx"一行l(wèi)og汗洒。

結(jié)合第2第3節(jié)的內(nèi)容,這里結(jié)果不對啊,這里外邊啟動協(xié)程時用的scope創(chuàng)建是用的已經(jīng)是SupervisorJob()了赫模,為什么當(dāng)中啟動的三個協(xié)程還會被提前取消蒸矛?

注意啊乡话,這里的啟動協(xié)程的封裝函數(shù)中傳入的this對象颅停,是coroutineScope中創(chuàng)建的子協(xié)程作用域而不是scope對象本身喊熟,所以思考的應(yīng)該是coroutineScope中創(chuàng)建的協(xié)程作用域是Job類型還是SupervisorJob類型钥勋,很遺憾,從結(jié)果上看骑冗,是Job類型(源碼上也是)谢翎。

所以,這時候啟動的3個協(xié)程的中結(jié)果沐旨,等同于第2節(jié)森逮,即一個協(xié)程拋出異常,最終會導(dǎo)致其父協(xié)程以及兄弟協(xié)程的取消磁携,從而使得最后一行l(wèi)og輸出的代碼不被執(zhí)行褒侧。

那如果希望3個協(xié)程的執(zhí)行互不干擾,想要第3節(jié)中類似的執(zhí)行結(jié)果谊迄,該如何闷供?很簡單,換用supervisorScope即可:

private fun suspendWithSupervisorJobClicked() {
    myLog("suspendWithSupervisorJobClicked")
    scope.launch {
        myLog("suspendWithSupervisorJobClicked parent coroutine")
        val ret = supervisorScope {
            launchTenSecondsCoroutine(this, "suspendWithSupervisorJobClicked A")
            launchTenSecondsCoroutine(this, "suspendWithSupervisorJobClicked B")
            launchCoroutineThrowException(this, "suspendWithSupervisorJobClicked")
            "supervisorScope final line"
        }
        myLog("suspendWithSupervisorJobClicked parent coroutine final line: $ret")
    }
}

問:這里的執(zhí)行結(jié)果能打印出來"suspendWithSupervisorJobClickedparent coroutine final line: xxx"這一行l(wèi)og嗎统诺?如果能歪脏,log輸出的時間點(diǎn)是在協(xié)程啟動后的不久后,還是5秒或10秒左右還是其他情況粮呢?最終的這一行l(wèi)og的拼接后的完整字符串內(nèi)容會是什么婿失?

答:能打印出來這行l(wèi)og,輸出的時間點(diǎn)在10秒后啄寡,最后一行l(wèi)og的完整字符串會是

"suspendWithSupervisorJobClickedparent coroutine final line: supervisorScope final line"豪硅。

這里,不妨簡單對比下coroutineScopesupervisorScope兩者挺物,因為這兩者才是真正的同一層面的可對比函數(shù):

  • 如果啟動的子協(xié)程中均沒有拋出異常懒浮,兩者在功能上沒有區(qū)別;
  • 如果啟動的子協(xié)程中任意一個某一時刻拋出了異常且用CoroutineExceptionHandler進(jìn)行處理识藤,那么前者的其他子協(xié)程會再異常拋出后被取消砚著,后者的其他子協(xié)程不受影響次伶;

附:目前見不少地方會用coroutineScopeCoroutineScope(context: CoroutineContext)這兩者作比較,事實(shí)上這兩者除了函數(shù)名起得特別像以后稽穆,函數(shù)的使用范圍冠王、設(shè)計功能都是截然不同的,并沒有什么可比較性秧骑。

6. 樣例工程代碼

代碼樣例Demo,見Github:https://github.com/TeaCChen/CoroutineStudy

本文示例代碼扣囊,如覺奇怪或啰嗦乎折,其實(shí)為SupervisorActivity.kt中的代碼摘取主要部分說明,在demo代碼當(dāng)中侵歇,為提升細(xì)節(jié)內(nèi)容骂澄,有更加多的封裝和輸出內(nèi)容。

本文的頁面截圖示例如下:

image-9-1.png

7. 補(bǔ)充說明

對于協(xié)程的異常處理惕虑,實(shí)際代碼開發(fā)當(dāng)中是要結(jié)合協(xié)程作用域坟冲、并發(fā)處理、協(xié)程取消協(xié)程等等內(nèi)容來進(jìn)一步按照需求來進(jìn)行設(shè)計溃蔫,協(xié)程當(dāng)中各種概念都不是孤島健提,往往講解一部分內(nèi)容的時候會設(shè)計到另一部分的補(bǔ)充設(shè)計,學(xué)習(xí)或使用協(xié)程的過程中請盡量保持好奇和探索伟叛。


基礎(chǔ)篇的內(nèi)容到此為止私痹,如果能看到這里,對于系列內(nèi)容中统刮,協(xié)程啟動紊遵、協(xié)程中切換線程、協(xié)程的取消侥蒙、協(xié)程作用域暗膜、掛起函數(shù)、結(jié)構(gòu)化并發(fā)鞭衩、協(xié)程異常等內(nèi)容有了個大概的實(shí)踐或認(rèn)識学搜,對于launchwithContext论衍、Dispatchers.IO恒水、Dispatchers.MainensureActive饲齐、CoroutineScope钉凌、joinasync捂人、await御雕、supervisorScope矢沿、CoroutineExceptionHandler等關(guān)鍵內(nèi)容有個大概的認(rèn)識。

基礎(chǔ)篇整體內(nèi)容酸纲,服務(wù)于對協(xié)程拿起就用的實(shí)用主義捣鲸,同時將協(xié)程的各項設(shè)計(協(xié)程作用域、掛起函數(shù)闽坡、取消栽惶、異常、結(jié)構(gòu)化并發(fā))從實(shí)踐代碼中逐步帶出疾嗅,為的是對協(xié)程使用有個較為系統(tǒng)的認(rèn)識外厂。

在協(xié)程的使用上,自然可以有“一看就會”這種更加通俗易懂的介紹內(nèi)容代承,而且也不用分九個部分來逐一實(shí)踐汁蝶,但是個人認(rèn)為,在協(xié)程的使用上论悴,欲速則不達(dá)掖棉。這一系列內(nèi)容,個人相信是一學(xué)就會的膀估,對于里面各種講解的各種情況幔亥,也給出了相應(yīng)的項目版完整代碼,有實(shí)踐察纯、有對比也有講解紫谷,剩下的,就是思考了捐寥。

我思故我在笤昨。
慢者,為快握恳。

一學(xué)就會的協(xié)程使用——基礎(chǔ)篇

一學(xué)就會的協(xié)程使用——基礎(chǔ)篇(一)協(xié)程啟動

一學(xué)就會的協(xié)程使用——基礎(chǔ)篇(二)線程切換

一學(xué)就會的協(xié)程使用——基礎(chǔ)篇(三)初遇協(xié)程取消

一學(xué)就會的協(xié)程使用——基礎(chǔ)篇(四)協(xié)程作用域

一學(xué)就會的協(xié)程使用——基礎(chǔ)篇(五)再遇協(xié)程取消

一學(xué)就會的協(xié)程使用——基礎(chǔ)篇(六)初識掛起

一學(xué)就會的協(xié)程使用——基礎(chǔ)篇(七)初識結(jié)構(gòu)化

一學(xué)就會的協(xié)程使用——基礎(chǔ)篇(八)初識協(xié)程異常

一學(xué)就會的協(xié)程使用——基礎(chǔ)篇(九)異常與supervisor(本文)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載瞒窒,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末乡洼,一起剝皮案震驚了整個濱河市崇裁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌束昵,老刑警劉巖拔稳,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異锹雏,居然都是意外死亡巴比,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轻绞,“玉大人采记,你說我怎么就攤上這事≌” “怎么了唧龄?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長奸远。 經(jīng)常有香客問我既棺,道長,這世上最難降的妖魔是什么懒叛? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任丸冕,我火速辦了婚禮,結(jié)果婚禮上芍瑞,老公的妹妹穿的比我還像新娘晨仑。我一直安慰自己褐墅,他們只是感情好拆檬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著妥凳,像睡著了一般竟贯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逝钥,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天屑那,我揣著相機(jī)與錄音,去河邊找鬼艘款。 笑死持际,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哗咆。 我是一名探鬼主播蜘欲,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼晌柬!你這毒婦竟也來了姥份?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤年碘,失蹤者是張志新(化名)和其女友劉穎澈歉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屿衅,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡埃难,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凯砍。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡箱硕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悟衩,到底是詐尸還是另有隱情剧罩,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布座泳,位于F島的核電站惠昔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏挑势。R本人自食惡果不足惜镇防,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望潮饱。 院中可真熱鬧来氧,春花似錦、人聲如沸香拉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凫碌。三九已至扑毡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盛险,已是汗流浹背瞄摊。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留苦掘,地道東北人换帜。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像鹤啡,于是被迫代替她去往敵國和親惯驼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容