AsyncSequence
是併發性框架和SE-298提案的一部分。它的名字意味着它是一個提供異步、順序和迭代訪問其元素的類型。換句話說:它是我們在Swift中熟悉的常規序列的一個異步變體。
就像你不會經常創建你的自定義序列一樣,我不期望你經常創建一個自定義的AsyncSequence
實現。然而,由於與AsyncThrowingStream和AsyncStream等類型一起使用,你很可能不得不與異步序列一起工作。因此,我將指導你使用AsyncSequence
實例進行工作。
什麼是 AsyncSequence?
AsyncSequence
是我們在Swift中熟悉的Sequence
的一個異步變體。由於它的異步性,我們需要使用await
關鍵字,因爲我們要處理的是異步定義的方法。如果你沒有使用過async/await
,我鼓勵你閱讀我的文章:Swift 中的 async/await。
值可以隨着時間的推移而變得可用,這意味着一個AsyncSequence
在你第一次使用它時可能不包含也可能包含一些,或者全部的值。
重要的是要理解AsyncSequence
只是一個協議。它定義瞭如何訪問值,但並不產生或包含值。AsyncSequence
協議的實現者提供了一個AsyncIterator
,並負責開發和潛在地存儲值。
創建一個自定義的 AsyncSequence
爲了更好地理解AsyncSequence
是如何工作的,我將演示一個實現實例。然而,在定義你的AsyncSequence
的自定義實現時,你可能想用AsyncStream
來代替,因爲它的設置更方便。因此,這只是一個代碼例子,以更好地理解AsyncSequence
的工作原理。
下面的例子沿用了原始提案中的例子,實現了一個計數器。這些值可以立即使用,所以對異步序列沒有太大的需求。然而,它確實展示了一個異步序列的基本結構:
struct Counter: AsyncSequence {
typealias Element = Int
let limit: Int
struct AsyncIterator : AsyncIteratorProtocol {
let limit: Int
var current = 1
mutating func next() async -> Int? {
guard !Task.isCancelled else {
return nil
}
guard current <= limit else {
return nil
}
let result = current
current += 1
return result
}
}
func makeAsyncIterator() -> AsyncIterator {
return AsyncIterator(howHigh: limit)
}
}
如您所見,我們定義了一個實現 AsyncSequence
協議的Counter
結構體。該協議要求我們返回一個自定義的 AsyncIterator
,我們使用內部類型解決了這個問題。我們可以決定重寫此示例以消除對內部類型的需求:
struct Counter: AsyncSequence, AsyncIteratorProtocol {
typealias Element = Int
let limit: Int
var current = 1
mutating func next() async -> Int? {
guard !Task.isCancelled else {
return nil
}
guard current <= limit else {
return nil
}
let result = current
current += 1
return result
}
func makeAsyncIterator() -> Counter {
self
}
}
我們現在可以將self
作爲迭代器返回,並保持所有邏輯的集中。
注意,我們必須通過提供typealias來幫助編譯器遵守AsyncSequence
協議。
next()
方法負責對整體數值進行迭代。我們的例子歸結爲提供儘可能多的計數值,直到我們達到極限。我們通過對Task.isCancelled
的檢查來實現取消支持。你可以在這裏閱讀更多關於任務和取消的信息。
異步序列的迭代
現在我們知道了什麼是AsyncSequence
以及它是如何實現的,現在是時候開始迭代這些值了。
以上述例子爲例,我們可以使用Counter
開始迭代:
for await count in Counter(limit: 5) {
print(count)
}
print("Counter finished")
// Prints:
// 1
// 2
// 3
// 4
// 5
// Counter finished
我們必須使用 await
關鍵字,因爲我們可能會異步接收數值。一旦不再有預期的值,我們就退出for循環。異步序列的實現者可以通過在next()
方法中返回nil
來表示達到極限。在我們的例子中,一旦計數器達到配置的極限,或者迭代取消,我們就會達到這個預期:
mutating func next() async -> Int? {
guard !Task.isCancelled else {
return nil
}
guard current <= limit else {
return nil
}
let result = current
current += 1
return result
}
許多常規的序列操作符也可用於異步序列。其結果是,我們可以以異步的方式執行映射和過濾等操作。
例如,我們可以只對偶數進行過濾:
for await count in Counter(limit: 5).filter({ $0 % 2 == 0 }) {
print(count)
}
print("Counter finished")
// Prints:
// 2
// 4
// Counter finished
或者我們可以在迭代之前將計數映射爲一個String
:
let counterStream = Counter(limit: 5)
.map { $0 % 2 == 0 ? "Even" : "Odd" }
for await count in counterStream {
print(count)
}
print("Counter finished")
// Prints:
// Odd
// Even
// Odd
// Even
// Odd
// Counter finished
我們甚至可以使用AsyncSequence
而不使用for循環,通過使用contains
等方法。
let contains = await Counter(limit: 5).contains(3)
print(contains) // Prints: true
注意,上述方法是異步的,意味着它有可能無休止地等待一個值的存在,直到底層的AsyncSequence
完成。
繼續你的Swift併發之旅
如果你喜歡你所讀到的關於異步序列的內容,你可能也會喜歡其他的併發主題:
- Swift 中的 async/await
- Swift 中的 async let
- Swift 中的 Task
- Swift 中的 Actors 使用以如何及防止數據競爭
- Swift 中的 MainActor 使用和主線程調度
- 理解 Swift Actor 隔離關鍵字:nonisolated 和 isolated
- Swift 中的 Sendable 和 @Sendable 閉包
- Swift 中的 AsyncThrowingStream 和 AsyncStream
- Swift 中的 AsyncSequence
結論
AsyncSequence
是我們在Swift中熟悉的常規Sequence
的異步替代品。就像你不會經常自己創建一個自定義Sequence
一樣,你也不太可能創建自定義的異步序列。相反,我建議你看一下AsyncStreams。