


หลายภาษาเช่น Kotlin, GO, JavaScript, Python, Rust, C#, C ++ และอื่น ๆ มีการสนับสนุน coroutines ที่ทำให้การใช้งาน Async/Await Await เป็นไปได้ คุณลักษณะนี้ยังไม่ได้รับการสนับสนุนใน Swift แต่สามารถปรับปรุงได้โดยเฟรมเวิร์กโดยไม่จำเป็นต้องเปลี่ยนภาษา
การเขียนโปรแกรมแบบอะซิงโครนัสมักจะเกี่ยวข้องกับการโทรกลับ มันค่อนข้างสะดวกจนกว่าจะมีพวกเขามากเกินไปและพวกเขาเริ่มทำรัง จากนั้นมันก็เรียกว่า ปิรามิดแห่งการลงโทษ หรือแม้แต่ การโทรกลับนรก
ปัญหาอีกประการหนึ่งของการเขียนโปรแกรมแบบอะซิงโครนัสคือ การจัดการข้อผิดพลาด เนื่องจากกลไกการจัดการข้อผิดพลาดตามธรรมชาติของ Swift ไม่สามารถใช้งานได้
มีเฟรมเวิร์กอื่น ๆ อีกมากมายที่ทำให้ง่ายต่อการใช้รหัสแบบอะซิงโครนัสเช่นการรวม Rxswift, PromiseKit และอื่น ๆ พวกเขาใช้วิธีการอื่น ๆ ที่มีข้อเสีย:
รูปแบบ Async/Await เป็นทางเลือกที่ช่วยให้ฟังก์ชั่นแบบอะซิงโครนัสและไม่ปิดกั้นได้รับการจัดโครงสร้างในลักษณะที่คล้ายกับฟังก์ชั่นซิงโครนัสสามัญ
เป็นที่ยอมรับได้ดีในภาษาการเขียนโปรแกรมอื่น ๆ และเป็นวิวัฒนาการในการเขียนโปรแกรมแบบอะซิงโครนัส การใช้งานรูปแบบนี้เป็นไปได้ด้วย coroutines
ลองดูตัวอย่างที่มี coroutine ภายในที่ await() ระงับและดำเนินการต่อเมื่อผลลัพธ์พร้อมใช้งานโดยไม่ต้องปิดกั้นเธรด
//executes coroutine on the main thread
DispatchQueue . main . startCoroutine {
//extension that returns CoFuture<(data: Data, response: URLResponse)>
let dataFuture = URLSession . shared . dataTaskFuture ( for : imageURL )
//await CoFuture result that suspends coroutine and doesn't block the thread
let data : Data = try dataFuture . await ( ) . data
//create UIImage from the data
guard let image = UIImage ( data : data ) else { return }
//execute heavy task on global queue and await the result without blocking the thread
let thumbnail : UIImage = try DispatchQueue . global ( ) . await { image . makeThumbnail ( ) }
//set image in UIImageView on the main thread
self . imageView . image = thumbnail
}เอกสาร API
coroutine เป็นการคำนวณที่สามารถระงับและกลับมาทำงานต่อในภายหลังโดยไม่ต้องปิดกั้นเธรด Coroutines สร้างตามฟังก์ชั่นปกติและสามารถดำเนินการกับตารางเวลาใด ๆ ที่มีความเป็นไปได้ที่จะสลับระหว่างพวกเขาในระหว่างการดำเนินการ
การออกแบบ Coroutines API นั้นเรียบง่ายที่สุดเท่าที่จะทำได้ ประกอบด้วยโปรโตคอล CoroutineScheduler ที่อธิบายวิธีกำหนดเวลา coroutines ( DispatchQueue สอดคล้องกับมัน) และโครงสร้าง Coroutine ด้วยวิธีการยูทิลิตี้ API นี้เพียงพอที่จะทำสิ่งที่น่าอัศจรรย์
ตัวอย่างต่อไปนี้แสดงการใช้งานของ await() ภายใน coroutine เพื่อห่อการโทรแบบอะซิงโครนัส
//execute coroutine on the main thread
DispatchQueue . main . startCoroutine {
//await URLSessionDataTask response without blocking the thread
let ( data , response , error ) = try Coroutine . await { callback in
URLSession . shared . dataTask ( with : url , completionHandler : callback ) . resume ( )
}
. . . use response on the main thread . . .
} นี่คือวิธีที่เราสามารถปฏิบัติตาม NSManagedObjectContext กับ CoroutineScheduler สำหรับการเปิดตัว coroutines บนนั้น
extension NSManagedObjectContext : CoroutineScheduler {
func scheduleTask ( _ task : @escaping ( ) -> Void ) {
perform ( task )
}
}
//execute coroutine on the main thread
DispatchQueue . main . startCoroutine {
let context : NSManagedObjectContext //context with privateQueueConcurrencyType
let request : NSFetchRequest < NSDictionary > //some complex request
//execute request on the context without blocking the main thread
let result : [ NSDictionary ] = try context . await { try context . fetch ( request ) }
}อนาคตคือผู้ถืออ่านอย่างเดียวสำหรับผลลัพธ์ที่จะให้ในภายหลังและสัญญาคือผู้ให้บริการของผลลัพธ์นี้ พวกเขาแสดงถึงความสำเร็จในที่สุดหรือความล้มเหลวของการดำเนินการแบบอะซิงโครนัส
อนาคตและสัญญาเข้าใกล้ตัวเองได้กลายเป็นอุตสาหกรรมที่ยืนหยัด มันเป็นกลไกที่สะดวกในการซิงโครไนซ์รหัสอะซิงโครนัส แต่เมื่อรวมกับ coroutines มันจะใช้การใช้รหัสแบบอะซิงโครนัสไปอีกระดับและได้กลายเป็นส่วนหนึ่งของรูปแบบ async/รอคอย หาก Coroutines เป็นโครงกระดูกฟิวเจอร์สและสัญญาคือกล้ามเนื้อ
ฟิวเจอร์สและสัญญาแสดงโดยคลาส CoFuture ที่สอดคล้องกันและคลาสย่อยของ CoPromise
//wraps some async func with CoFuture
func makeIntFuture ( ) -> CoFuture < Int > {
let promise = CoPromise < Int > ( )
someAsyncFunc { int in
promise . success ( int )
}
return promise
} อนุญาตให้เริ่มงานหลายงานแบบขนานและซิงโครไนซ์ในภายหลังด้วย await()
//create CoFuture<Int> that takes 2 sec. from the example above
let future1 : CoFuture < Int > = makeIntFuture ( )
//execute coroutine on the global queue and returns CoFuture<Int> with future result
let future2 : CoFuture < Int > = DispatchQueue . global ( ) . coroutineFuture {
try Coroutine . delay ( . seconds ( 3 ) ) //some work that takes 3 sec.
return 6
}
//execute coroutine on the main thread
DispatchQueue . main . startCoroutine {
let sum : Int = try future1 . await ( ) + future2 . await ( ) //will await for 3 sec.
self . label . text = " Sum is ( sum ) "
} มันง่ายมากที่จะแปลงหรือเขียน CoFuture s เป็นใหม่
let array : [ CoFuture < Int > ]
//create new CoFuture<Int> with sum of future results
let sum = CoFuture { try array . reduce ( 0 ) { try $0 + $1 . await ( ) } }ฟิวเจอร์สและสัญญาเป็นวิธีที่สะดวกในการถ่ายโอนค่าเดียวระหว่าง coroutines แชนเนลให้วิธีการถ่ายโอนกระแสของค่า แนวคิดช่องทางคล้ายกับคิวที่อนุญาตให้ระงับ coroutine เมื่อได้รับหากว่างเปล่าหรือส่งถ้ามันเต็ม
แบบดั้งเดิมที่ไม่ปิดกั้นนี้ใช้กันอย่างแพร่หลายในภาษาเช่น GO และ Kotlin และเป็นเครื่องมืออื่นที่ปรับปรุงการทำงานกับ coroutines
ในการสร้างช่องให้ใช้คลาส CoChannel
//create a channel with a buffer which can store only one element
let channel = CoChannel < Int > ( capacity : 1 )
DispatchQueue . global ( ) . startCoroutine {
for i in 0 ..< 100 {
//imitate some work
try Coroutine . delay ( . seconds ( 1 ) )
//sends a value to the channel and suspends coroutine if its buffer is full
try channel . awaitSend ( i )
}
//close channel when all values are sent
channel . close ( )
}
DispatchQueue . global ( ) . startCoroutine {
//receives values until closed and suspends a coroutine if it's empty
for i in channel . makeIterator ( ) {
print ( " Receive " , i )
}
print ( " Done " )
} Coroutines ที่เปิดตัวทั้งหมด CoFuture S และ CoChannel S มักจะไม่จำเป็นต้องอ้างอิง พวกเขาถูก deinited หลังจากดำเนินการ แต่บ่อยครั้งที่มีความจำเป็นที่จะต้องทำให้เสร็จก่อนหน้านี้เมื่อพวกเขาไม่ต้องการอีกต่อไป สำหรับสิ่งนี้ CoFuture และ CoChannel มีวิธีการยกเลิก
CoScope ทำให้การจัดการวงจรชีวิตของวัตถุเหล่านี้ง่ายขึ้น ช่วยให้คุณสามารถอ้างอิงที่อ่อนแอและยกเลิกหากจำเป็นหรือใน deinit
คุณสามารถเพิ่ม coroutines, CoFuture S, CoChannel S และ CoCancellable อื่น ๆ ลงใน CoScope เพื่อยกเลิกพวกเขาเมื่อพวกเขาไม่ต้องการอีกต่อไปหรือใน deinit
class ViewController : UIViewController {
let scope = CoScope ( ) //will cancel all objects on `cancel()` or deinit
func performSomeWork ( ) {
//create new `CoChannel` and add to `CoScope`
let channel = makeSomeChannel ( ) . added ( to : scope )
//execute coroutine and add to `CoScope`
DispatchQueue . main . startCoroutine ( in : scope ) { [ weak self ] in
for item in channel . makeIterator ( ) {
try self ? . performSomeWork ( with : item )
}
}
}
func performSomeWork ( with item : Item ) throws {
//create new `CoFuture` and add to `CoScope`
let future = makeSomeFuture ( item ) . added ( to : scope )
let result = try future . await ( )
. . . do some work using result . . .
}
}