Swift 中異步 即將支持的Async/Await

原文:Async/Await for Swift

介紹 :現(xiàn)代Cocoa開發(fā)涉及到很多使用閉包和completion handlers的異步編程糯彬,但這些API很難用伪很。當使用許多異步操作姜钳,中間有錯誤處理或異步調(diào)用的控制流的時候,問題會變得特別復(fù)雜瞳遍。針對上面的問題压彭,提出了一個對swift的擴展睦优,讓swift處理異步更加自然,不易出錯壮不。 本文介紹了一個Swift一流的協(xié)程(維基百科)模型汗盘。函數(shù)可以使用async關(guān)鍵詞,讓編寫涉及異步操作的復(fù)雜邏輯通過編譯器自動生成必要的閉包和狀態(tài)機來實現(xiàn)該邏輯询一。 這一點非常重要隐孽,首先它提出了runtime-agnostic(運行時無感知的)完全并發(fā)的編譯器支持。并不是要建立一個新的運行時模型(比如“actors”的參與者的模型,另外提一句健蕊,之前喵神Swift 并行編程現(xiàn)狀和展望 - async/await 和參與者模式有提到的swift異步編程可能會使用actors模型看來并不會菱阵,而是使用async關(guān)鍵詞)。并且它和GCD一樣能很好的使用pthread或者其他的API缩功。此外送粱,與其他語言的設(shè)計不同,它獨立于特定的協(xié)調(diào)機制(例如futures或channels)掂之,將這些機制作建立為語言庫的一個特征。唯一需要做的就是利用運行時來支持轉(zhuǎn)換和操作隱式生成的閉包脆丁,而這些工作將會通過編譯器特性來支持實現(xiàn)對應(yīng)的邏輯世舰。這些內(nèi)容是Chris LattnerJoe GroffOleg Andreev撰寫的早期提案中得到了一些啟示,經(jīng)過大改重寫的提議槽卫。

動機: Completion handlers并不理想
為了展示Completion遭遇的事情跟压,并且這些問題還是很重要的.我們來看看在Cocoa(包括服務(wù)端/云)編程過程中經(jīng)常碰的問題。
問題1: 厄運金字塔(回調(diào)地獄 callback hell) 在嵌套塊中執(zhí)行一些簡單的操作顯得特別不自然歼培。 下面是例子:


func processImageData1(completionBlock: (result: Image) -> Void) { 

    loadWebResource("dataprofile.txt") { 

        dataResource in loadWebResource("imagedata.dat") {

             imageResource in decodeImage(dataResource, imageResource) { 

                imageTmp in dewarpAndCleanupImage(imageTmp) {

                     imageResult in completionBlock(imageResult)

                 }

             }

         }

     }

}

processImageData1 { image in display(image)} 

這個嵌套的回調(diào)使得問題追蹤變得很困難震蒋,并且閉包堆棧還引帶來一步的影響。

問題2:錯誤處理錯誤處理也變得困難躲庄,并且很麻煩查剖。Swift 2為同步代碼引入了一個錯誤處理的模型,但是基于回調(diào)的接口這么處理并不好.


func processImageData2(completionBlock: (result: Image?, error: Error?) -> Void) {     loadWebResource("dataprofile.txt") { 

        dataResource, error in guard let dataResource = dataResource else {

             completionBlock(nil, error) 

             return

         }

         loadWebResource("imagedata.dat") { 

                  imageResource, error in guard let imageResource = imageResource else { 

                    completionBlock(nil, error) 

                     return

             } 

             decodeImage(dataResource, imageResource) { 

                    imageTmp, error in guard let imageTmp = imageTmp else {

                         completionBlock(nil, error)

                          return 

                     }

                dewarpAndCleanupImage(imageTmp) {

                     imageResult in guard let imageResult = imageResult else {

                         completionBlock(nil, error) return } completionBlock(imageResult)

                     }

                 }

             }

         }

    }

processImageData2 { 

    image, error in guard let image = image else {

         error("No image today")

          return

     }

 display(image)

}

問題3:條件執(zhí)行很困難并且容易出錯條件地執(zhí)行一個異步函數(shù)是非常的痛苦噪窘∷褡或許最好的辦法是在條件執(zhí)行的閉包(協(xié)助的閉包)中寫入一半的代碼,如下所示:


func processImageData3(recipient: Person, completionBlock: (result: Image) -> Void) {

     let continuation: (contents: image) -> Void = {

     // ... continue and call completionBlock eventually

     }

 if recipient.hasProfilePicture { 

     continuation(recipient.profilePicture)

 }

 else {

     decodeImage { 

        image in continuation(image) 

         }

     }

}

問題4:很容易出錯很容易就return掉了,忘記調(diào)用那個協(xié)助的block直砂,并且這個問題很難調(diào)試


func processImageData4(completionBlock: (result: Image?, error: Error?) -> Void) {     loadWebResource("dataprofile.txt") { 

        dataResource, error in guard let dataResource = dataResource else { 

             return // <- 忘記調(diào)用block 

         }

        loadWebResource("imagedata.dat") { 

            imageResource, error in guard let imageResource = imageResource else {

                 return // <- 忘記調(diào)用block 

             } 

         ...

         }

    }

}

當你不忘記調(diào)用的時候菌仁,你還有可以忘記會要return。慶幸的是静暂,在某種程度上guard語法可以幫助你济丘,但并不是每一次都奏效。


func processImageData5(recipient:Person, completionBlock: (result: Image?, error: Error?) -> Void) {

     if recipient.hasProfilePicture { 

         if let image = recipient.profilePicture { 

             completionBlock(image) // <- 調(diào)用block之后忘了 return

             }

         }

         ...

}

問題5:由于completion handlers很笨拙而定義了太多的API(抱歉這里沒有看太明白直接貼原文)This is hard to quantify, but the authors believe that the awkwardness of defining and using asynchronous APIs (using completion handlers) has led to many APIs being defined with apparently synchronous behavior, even when they can block. This can lead to problematic performance and responsiveness problems in UI applications - e.g. spinning cursor. It can also lead to the definition of APIs that cannot be used when asynchrony is critical to achieve scale, e.g. on the server.

問題6:其他“可恢復(fù)”的行為很難定義這里“可恢復(fù)”行為比如下面:要編寫生成一組數(shù)的平方的列表的代碼洽蛀,可以這樣寫:for i in 1...10 { print(i*i)}摹迷、但是,如果你想把它寫成Swift sequence辱士,你必須將其定義成可迭代的方式去生成值泪掀。 有多種方法可以做到這一點(例如,使用AnyIterator或序列(state:next :)函數(shù))颂碘,但是沒有一種方法能夠?qū)崿F(xiàn)命令式的清晰和明顯异赫。相反,具有g(shù)enerators的語言允許你寫更接近這個的東西:


func getSequence() -> AnySequence{

     let seq = sequence {

         for i in 1...10 {

             yield(i*i)

         }

     } 

 return AnySequence(seq)

}

編譯器有責任通過生成狀態(tài)機將函數(shù)轉(zhuǎn)換為迭代式生成值的形式头岔。

建議的方案:協(xié)程這些問題在許多系統(tǒng)和許多語言中都已經(jīng)面臨塔拳,而協(xié)程的抽象是解決它們的標準方法。在不深入理論的情況下峡竣,協(xié)程是允許函數(shù)返回值或被暫停的基本函數(shù)的擴展靠抑。它們可以用來實現(xiàn)生成器,異步模型和其他功能 - 理論适掰,實現(xiàn)和優(yōu)化都有大量的工作颂碧。這個提議增加了對Swift的一般協(xié)程支持,使用最常見的方式:定義和使用異步API类浪,消除了許多使用完成處理程序的問題载城。關(guān)鍵詞的選擇(async與yields)是一個需要解決的話題,但與模型的核心語義無關(guān)费就。最后诉瓦,請參閱“Alternate Syntax Options”了解此其中的語法操作。很重要的一點是你需要提前知道這個提議的協(xié)程模型并不與特定系統(tǒng)上的任何特定的并發(fā)接口相關(guān):你可以把它看作完成completion handlers的語法糖力细。像其他系統(tǒng)一樣睬澡,這意味著引入?yún)f(xié)程并不會改變completion handlers執(zhí)行的的隊列。異步語法現(xiàn)在眠蚂,函數(shù)的類型可以是正常的或者是可以throw的煞聪,這個提議指出函數(shù)類型同樣可以使用async。下面的函數(shù)都是正確的


 (Int) -> Int // #1: Normal function

 (Int) throws -> Int // #2: Throwing function

 (Int) async -> Int // #3: Asynchronous function

 (Int) async throws -> Int // #4: Asynchronous function, can also throw

就像普通函數(shù)(#1)隱式轉(zhuǎn)換為拋出函數(shù)(#2)河狐,異步函數(shù)(#3)隱式轉(zhuǎn)換為拋出異步函數(shù)(#4)米绕。在函數(shù)的聲明方面瑟捣,你可以聲明一個函數(shù)是異步的,就像你聲明它是拋出一樣栅干,但是使用async關(guān)鍵字:


func processImageData() async -> Image { ... }

//語法類似于這個:

func processImageData(completionHandler: (result: Image) -> Void) { ... }

調(diào)用異步函數(shù)可以隱式掛起當前的協(xié)程迈套。 為了讓代碼維護者明白這一點,你需要用新的await關(guān)鍵字“標記”調(diào)用異步函數(shù)的表達式(類似于用try來標記包含拋出調(diào)用的子表達式)碱鳞。 把這兩部分放在一起桑李,第一個例子(上面回調(diào)嵌套的版本)可以用更自然的方式重寫:


func loadWebResource(_ path: String) async -> Resource

func decodeImage(_ r1: Resource, _ r2: Resource) async -> Image

func dewarpAndCleanupImage(_ i : Image) async -> Image

func processImageData1() async -> Image { 

     let dataResource = await loadWebResource("dataprofile.txt") 

     let imageResource = await loadWebResource("imagedata.dat")

     let imageTmp = await decodeImage(dataResource, imageResource)

     let imageResult = await dewarpAndCleanupImage(imageTmp)

     return imageResult

}

在內(nèi)部,編譯器使用上面的例子processImageData1中的嵌套閉包來重寫這段代碼窿给。注意了贵白,每個操作只有在前一個操作完成后才會啟動,但是每個調(diào)用異步函數(shù)的站點都可以暫停當前函數(shù)的執(zhí)行崩泡。最后禁荒,只允許從另一個異步函數(shù)或閉包中調(diào)用異步函數(shù)。這里使用Swift 2錯誤處理的模型你不能調(diào)用拋出函數(shù)角撞,除非你在拋出函數(shù)中呛伴,或者在do / catch塊中。進入和退出異步代碼在通常情況下谒所,異步代碼應(yīng)該調(diào)用由的其他異步代碼完成热康,但是在某個時刻,異步過程需要自己控制同步上下文的異步過程劣领,需要能夠暫停自己姐军,并允許其控制上下文的繼續(xù)。 我們需要一些函數(shù)來啟用和暫停異步上下文:


// NB: Names subject to bikeshedding. These are low-level primitives that most

// users should not need to interact with directly, so namespacing them

// and/or giving them verbose names unlikely to collide or pollute code

// completion (and possibly not even exposing them outside the stdlib to begin

// with) would be a good idea.

/// Begins an asynchronous coroutine, transferring control to `body` until it

/// either suspends itself for the first time with `suspendAsync` or completes,

/// at which point `beginAsync` returns. If the async process completes by

/// throwing an error before suspending itself, `beginAsync` rethrows the error.func beginAsync(_ body: () async throws -> Void) rethrows -> Void

/// Suspends the current asynchronous task and invokes `body` with the task's

/// continuation closure. Invoking `continuation` will resume the coroutine

/// by having `suspendAsync` return the value passed into the continuation.

/// It is a fatal error for `continuation` to be invoked more than once.func suspendAsync( _ body: (_ continuation: @escaping (T) -> ()) -> ()) async -> T

/// Suspends the current asynchronous task and invokes `body` with the task's

/// continuation and failure closures. Invoking `continuation` will resume the

/// coroutine by having `suspendAsync` return the value passed into the

/// continuation. Invoking `error` will resume the coroutine by having

/// `suspendAsync` throw the error passed into it. Only one of

/// `continuation` and `error` may be called; it is a fatal error if both are

/// called, or if either is called more than once.

func suspendAsync(

     _ body: (_ continuation: @escaping (T) -> (), _ 

    error: @escaping (Error) -> ()

) -> ()) async throws -> T

這些與"delimited continuations"的“shift”和“reset”類似尖淘。 這些允許非異步函數(shù)來調(diào)用異步函數(shù)奕锌。 例如,用completion handlers編寫的@IBAction:


@IBAction func buttonDidClick(sender:AnyObject) {

 // 1 processImage(completionHandler: {(image) in 

 // 2 imageView.image = image }) 

 // 3}

This is an essential pattern, but is itself sort of odd: an async operation is being fired off immediately (#1), then runs the subsequent code (#3), and the completion handler (#2) runs at some time later -- on some queue (often the main one). This pattern frequently leads to mutation of global state (as in this example) or to making assumptions about which queue the completion handler is run on. Despite these problems, it is essential that the model encompasses this pattern, because it is a practical necessity in Cocoa development. With this proposal, it would look like this:


@IBAction func buttonDidClick(sender:AnyObject) { 

 // 1 beginAsync { 

 // 2 let image = await processImage() imageView.image = image

 }

 // 3

}

這些函數(shù)能通過基于回到的api封裝成異步的協(xié)程api


// Legacy callback-based API

func getStuff(completion: (Stuff) -> Void) { ... }

// Swift wrapper

func getStuff() async -> Stuff {

     return await suspendAsync {

         continuation in getStuff(completion: continuation) 

    }

}

比如 libdispatch 和 pthread 這種函數(shù)式的并發(fā)的庫 同樣可以通過協(xié)程的形式很友好的提供接口


extension DispatchQueue {

 /// Move execution of the current coroutine synchronously onto this queue.

     func syncCoroutine() async -> Void { 

         await suspendAsync {

             continuation in sync { continuation

         }

     }

 }

 /// Enqueue execution of the remainder of the current coroutine 

 /// asynchronously onto this queue.

 func asyncCoroutine() async -> Void { 

     await suspendAsync {

         continuation in async { 

            continuation

         }

     }

 }

}

func queueHopping() async -> Void { 

     doSomeStuff()

     await DispatchQueue.main.syncCoroutine() 

     doSomeStuffOnMainThread() 

    await backgroundQueue.asyncCoroutine()  

    doSomeStuffInBackground()

}

也可以建立協(xié)調(diào)協(xié)程的通用抽象形式村生。 其中最簡單的就是future(類似promise)歇攻,future中的值可能還沒有沒有resolved(不明白的同學可以搜索一下promise 或者future的原理,有興趣的同學可以去看一下promiseKit)梆造。 Future的確切設(shè)計超出了這個提議的范圍(應(yīng)該是它自己的后續(xù)提議),但是概念證明的例子可能是這樣的:


class Future{

  private enum Result { case error(Error), value(T) }

  private var result: Result? = nil

  private var awaiters: [(Result) -> Void] = []

// Fulfill the future, and resume any coroutines waiting for the value.

  func fulfill(_ value: T) {

    precondition(self.result == nil, "can only be fulfilled once")

    let result = .value(value)

    self.result = result

    for awaiter in awaiters {

      // A robust future implementation should probably resume awaiters

      // concurrently into user-controllable contexts. For simplicity this

      // proof-of-concept simply resumes them all serially in the current

      // context.

      awaiter(result)
    
    }

    awaiters = []

  }

  // Mark the future as having failed to produce a result.

  func fail(_ error: Error) {

    precondition(self.result == nil, "can only be fulfilled once")

    let result = .error(error)

    self.result = result

    for awaiter in awaiters {

      awaiter(result)

    }

    awaiters = []

  }

  func get() async throws -> T {

    switch result {

      // Throw/return the result immediately if available.

      case .error(let e)?:

        throw e

      case .value(let v)?:

        return v

      // Wait for the future if no result has been fulfilled.

      case nil:

        return await suspendAsync {
           continuation, error in

          awaiters.append({

          switch $0 {

            case .error(let e): error(e)

            case .value(let v): continuation(v)

          }

        })

    }

  }

  }

  // Create an unfulfilled future.

  init() {}

  // Begin a coroutine by invoking `body`, and create a future representing

  // the eventual result of `body`'s completion.

  convenience init(_ body: () async -> T) {

    self.init()

    beginAsync {

      do {

        self.fulfill(await body())

      } catch {

        self.fail(error)

      }

    }

  }

}

重申一下葬毫,眾所周知镇辉,這個具體的實現(xiàn)有性能和API的缺點,只是簡單地描述一下像這樣的抽象可以建立在async/await之上贴捡。

Futrues允許并行執(zhí)行忽肛,在需要時將等待調(diào)用的結(jié)果移動到結(jié)果中,并將并行調(diào)用包裝在各個Future對象中:(這里提一下烂斋,及時從nodejs的實踐中我的體會是通過單純的promise或者future鏈式調(diào)用的時候回出現(xiàn)過個連續(xù)的參數(shù)無法再同一個作用域中使用屹逛,所以封裝出async和await是很必要的一個方式础废,有興趣的同學可以看es7中大家關(guān)于async和await的討論)


func processImageData1a() async -> Image {

let dataResource  = Future { await loadWebResource("dataprofile.txt") }

let imageResource = Future { await loadWebResource("imagedata.dat") }

// ... other stuff can go here to cover load latency...

let imageTmp    = await decodeImage(dataResource.get(), imageResource.get())

let imageResult = await dewarpAndCleanupImage(imageTmp)

return imageResult

}

在上面的例子中,前兩個操作會一個接一個地開始罕模,而未求值的計算被包裝成Future值评腺。 這允許它們?nèi)客瑫r發(fā)生(不需要由語言或Future實現(xiàn)定義的方式),并且在解碼圖像之前淑掌,函數(shù)將等待它們完成蒿讥。 請注意,await不會阻止執(zhí)行流程:如果該值尚未準備就緒抛腕,則暫停當前異步函數(shù)的執(zhí)行芋绸,并將控制流程傳遞到堆棧中較高的位置。

其他協(xié)調(diào)抽象担敌,如 Communicating Sequential Process channels 或者 Concurrent ML events也可以作為協(xié)調(diào)協(xié)程的庫來開發(fā); 他們的實現(xiàn)留給讀者作為練習摔敛。

轉(zhuǎn)換導(dǎo)入的objective-c 的APIs

完整的細節(jié)不在本提案的范圍之內(nèi),但重要的是要增強導(dǎo)入器將基于Objective-C completion-handler的API映射到async中全封。 相當于NSError **函數(shù)作為throws函數(shù)導(dǎo)入的轉(zhuǎn)換马昙。 引進這些意味著許多Cocoa API將會更加現(xiàn)代化。

有多種可能的設(shè)計與權(quán)衡售貌。 完成這項工作通過最根源的方式是以兩種形式完成基于completion-handler的API的轉(zhuǎn)換:completion-handler和async之間的轉(zhuǎn)化给猾。 例如:


// Before

- (void) processImageData:(void(^)())completionHandler;

- (void) processImageData:(void(^)(Image* __nonnull image))completionHandler;

- (void) processImageData:(void(^)(Image* __nullable image1, NSError* __nullable error))completionHandler;

- (void) processImageData:(void(^)(Image* __nullable half1, Image* __nullable half2, NSError* __nullable error))completionHandler;

- (void) processImageData:(void(^)(NSError* __nullable error))completionHandler;

上面的聲明都是以正常completion-handler的,而且也是以更好的async改寫:


func processImageData() async

func processImageData() async -> Image

func processImageData() async throws -> Image

func processImageData() async throws -> (half1: Image, half2: Image)

func processImageData() async throws

應(yīng)該將一下這些細節(jié)定義為引入這一特性過程的一部分颂跨,例如:

什么是轉(zhuǎn)化的確切規(guī)則敢伸?

多個結(jié)果函數(shù)是否能夠自動處理?

在Swift 5模式下completion-handler只能作為async恒削,強制遷移會更好嗎池颈?

應(yīng)該怎樣處理non-Void-returning completion handler(例如在URLSession)?

是否應(yīng)該把用于觸發(fā)響應(yīng)事件的異步操作(如IBAction方法)的Void方法應(yīng)改為async - > Void钓丰?

Without substantial ObjC importer work, making a clean break and forcing migration in Swift 5 mode would be the most practical way to preserve overridability, but would create a lot of churn in 4-to-5 migration. Alternatively, it may be acceptable to present the async versions as final wrappers over the underlying callback-based interfaces; this would subclassers to work with the callback-based interface, but there are generally fewer subclassers than callers.

(很難看明白)

如果沒有大量的ObjC轉(zhuǎn)換的工作躯砰,在Swift 5下進行徹底強制遷移將是保持可覆蓋性的最實際的方法,但是會在4到5的遷移中產(chǎn)生大量的改變携丁∽列或者,可以接受的情況是將async版本作為基于回調(diào)的接口上的最終(final)的封裝版本;

與現(xiàn)有功能交互

這個提議與Swift中現(xiàn)有的語言特性很契合梦鉴,下面是幾個例子:

錯誤處理

在Swift 2中引入的錯誤處理語法自然而然地與這個異步模型相結(jié)合李茫。


// Could throw or be interrupted:

func processImageData() async throws -> Image

// Semantically similar to:

func processImageData(completionHandler: (result: Image?, error: Error?) -> Void)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市肥橙,隨后出現(xiàn)的幾起案子魄宏,更是在濱河造成了極大的恐慌,老刑警劉巖存筏,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宠互,死亡現(xiàn)場離奇詭異味榛,居然都是意外死亡,警方通過查閱死者的電腦和手機予跌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門搏色,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人匕得,你說我怎么就攤上這事继榆。” “怎么了汁掠?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵略吨,是天一觀的道長。 經(jīng)常有香客問我考阱,道長翠忠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任乞榨,我火速辦了婚禮秽之,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吃既。我一直安慰自己考榨,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布鹦倚。 她就那樣靜靜地躺著河质,像睡著了一般。 火紅的嫁衣襯著肌膚如雪震叙。 梳的紋絲不亂的頭發(fā)上掀鹅,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音媒楼,去河邊找鬼乐尊。 笑死,一個胖子當著我的面吹牛划址,可吹牛的內(nèi)容都是我干的扔嵌。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼夺颤,長吁一口氣:“原來是場噩夢啊……” “哼对人!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拂共,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姻几,沒想到半個月后宜狐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體势告,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年抚恒,在試婚紗的時候發(fā)現(xiàn)自己被綠了咱台。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡俭驮,死狀恐怖回溺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情混萝,我是刑警寧澤遗遵,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站逸嘀,受9級特大地震影響车要,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜崭倘,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一翼岁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧司光,春花似錦琅坡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至跪削,卻和暖如春谴仙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碾盐。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工晃跺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人毫玖。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓掀虎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親付枫。 傳聞我的和親對象是個殘疾皇子烹玉,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345