GCD 基本概念延伸-Group、Semaphore、Timer、asyncAfter
此篇延續上一篇 GCD 基本概念 簡介各種應用情境,例如使用 DispatchSourceTimer 進行精確計時,asyncAfter 在未來時間執行任務,DispatchGroup 確保多個異步任務完成後進行統一處理,DispatchSemaphore 控制並行程式碼的執行順序。
- asyncAfter
- DispatchSourceTimer
- DispatchGroup
- DispatchSemaphore
asyncAfter
用於在特定的時間後異步執行任務。它可以用來在未來的某個時間點執行一段程式碼。
wallDeadline
: 這是一個相對於系統啟動時間(即不受休眠和時間更改影響)的時間截止點。這樣的時間截止點通常用於需要精確計時,不受系統時間變更的影響的情況。例如,用於實現某個計時器功能。
let wallDeadline = DispatchWallTime.now() + .seconds(5)
DispatchQueue.global().asyncAfter(wallDeadline: wallDeadline) {
print("This code will be executed after 5 seconds using wall time.")
}
deadline
: 這是一個相對於系統的可休眠時間的時間截止點。這表示在休眠時,計時器會暫停,而不是繼續計時。這種時間截止點通常用於需要在休眠時保持相對時間的情況。
let deadline = DispatchTime.now() + .seconds(5)
DispatchQueue.global().asyncAfter(deadline: deadline) {
print("This code will be executed after 5 seconds using regular time.")
}
DispatchSourceTimer
DispatchSourceTimer
是 GCD 中的一個計時器工具,用於執行重複性或一次性的任務。相對於 Timer
更為精確,原因在於 Timer
受 Runloop 影響,如果 Timer
到了觸發時間,而這時候Runloop 正在處理其他耗時的任務,那麼本次 Timer
的調用會被漏掉。
class TimerExample {
private var timer: DispatchSourceTimer?
func startTimer() {
let queue = DispatchQueue.global(qos: .background)
timer = DispatchSource.makeTimerSource(queue: queue)
// 設定初次執行時間、間隔時間
timer?.schedule(deadline: .now(), repeating: .seconds(1), leeway: .milliseconds(100))
// 設定計時器執行的任務
timer?.setEventHandler { [weak self] in
self?.timerFired()
}
// 啟動計時器
timer?.resume()
}
func stopTimer() {
// 取消計時器
timer?.cancel()
timer = nil
}
private func timerFired() {
// 計時器執行的任務
print("Timer fired at \\(Date())")
}
}
let timerExample = TimerExample()
timerExample.startTimer()
DispatchGroup
DispatchGroup
主要分成 enter(), leave()
和隊列調用 group
兩種方式,可以很方便的管理多項任務。比如多個網路請求同時發出去,等網路請求都完成後 reload UI
隊列調用 group
在這個示例中,我們首先創建一個 DispatchGroup
,然後在一個 concurrent queue 上啟動了兩個異步任務。這兩個任務都與 group
相關聯,這意味著我們告訴 group
這些任務屬於同一個組。
let group = DispatchGroup()
let queue = DispatchQueue(label: "com.custom.queue", attributes: .concurrent)
// 開始第一個異步任務
queue.async(group: group) {
print("Task 1 started")
Thread.sleep(forTimeInterval: 2) // 模擬耗時操作
print("Task 1 finished")
}
// 開始第二個異步任務
queue.async(group: group) {
print("Task 2 started")
Thread.sleep(forTimeInterval: 1) // 模擬耗時操作
print("Task 2 finished")
}
// 等待所有任務完成
group.notify(queue: .main) {
print("All tasks are finished")
}
輸出結果如下:可以發現當兩個異步任務都完成時才會觸發 notify
方法
Task 2 started
Task 1 started
Task 2 finished
Task 1 finished
All tasks are finished
將操作改在異步任務執行
let group = DispatchGroup()
let queue = DispatchQueue(label: "concurrent", attributes: .concurrent)
queue.async(group: group) {
DispatchQueue.global().async {
print("Task 1 started")
Thread.sleep(forTimeInterval: 2) // 模擬耗時操作
print("Task 1 finished")
}
}
queue.async(group: group) {
DispatchQueue.global().async {
print("Task 2 started")
Thread.sleep(forTimeInterval: 2) // 模擬耗時操作
print("Task 2 finished")
}
}
// 等待所有任務完成
group.notify(queue: .main) {
print("All tasks are finished")
}
輸出結果如下:可以發現會直接觸發notify
方法,而不會等待異步任務都完成後
Task 1 started
Task 2 started
All tasks are finished
Task 2 finished
Task 1 finished
enter、leave
DispatchGroup
中的 enter()
和 leave()
方法用於控制 Dispatch Group 中任務的進入和離開,以確保你可以正確地等待所有任務完成。
enter()
:當一個異步任務要加入到 Dispatch Group 中時,應該在任務的開始處調用enter()
方法。這告訴 Dispatch Group,有一個新的任務要進入,需要追蹤它。leave()
:當一個異步任務完成時,應該在任務的結尾處調用leave()
方法。這告訴 Dispatch Group,該任務已經完成,可以將它從組中移除。
以下是使用 enter()
和 leave()
的示例:
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .background)
group.enter() // 進入組
queue.async {
print("Task 1 started")
Thread.sleep(forTimeInterval: 2) // 模擬耗時操作
print("Task 1 finished")
group.leave() // 離開組
}
group.enter() // 進入組
queue.async {
print("Task 2 started")
Thread.sleep(forTimeInterval: 1) // 模擬耗時操作
print("Task 2 finished")
group.leave() // 離開組
}
// 等待所有任務完成
group.notify(queue: .main) {
print("All tasks are finished")
}
這個示例中,我們使用 enter()
和 leave()
在每個任務的開始和結尾處來管理 group
。這確保 group
知道何時任務加入和離開,以便在所有任務完成時觸發 notify
回調。
DispatchSemaphore
用於控制並行程式碼的執行順序。它是一種計數信號量,用於限制同一時間允許訪問資源的執行緒或程式碼的數量。
DispatchSemaphore
具有兩個主要操作:
wait()
:當呼叫wait
時,如果計數器為零,它將阻止當前的執行緒,直到計數器增加為止。如果計數器大於零,則將計數器減一,並允許程式碼繼續執行。signal()
:當呼叫signal
時,它將增加計數器的值。如果有一個或多個執行緒正在等待wait
,則其中一個將繼續執行。
假設你有一個資源,例如一個共享的資料結構,你想確保在同一時間最多只有三個執行緒可以訪問這個資源。你可以使用 DispatchSemaphore
來實現這一點。
let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
let semaphore = DispatchSemaphore(value: 3)
func accessSharedResource(index: Int) {
semaphore.wait() // 等待信號量,如果信號量已經達到上限,則會阻止執行緒
concurrentQueue.async {
print("Accessing resource \\(index)")
// 執行某些操作,訪問共享資源
print("訪問資源 \\(index)")
// ...
semaphore.signal() // 釋放信號量,表示訪問結束,其他執行緒可以進入
}
}
for i in 0..<10 {
accessSharedResource(index: i)
}
在這個例子中,我們創建了一個名為 concurrentQueue
的併發隊列,並初始化了一個 DispatchSemaphore
,其初始值為 3。然後,我們使用一個 for 迴圈啟動 10 個執行緒,每個執行緒嘗試訪問共享資源,但由於信號量的限制,同一時間最多允許三個執行緒同時訪問資源。其他執行緒必須等待,直到有一個執行緒釋放信號量後,才能進入臨界區。這樣可以確保資源的有序訪問。
Accessing resource 0
Accessing resource 1
訪問資源 0
訪問資源 1
Accessing resource 2
Accessing resource 3
訪問資源 2
訪問資源 3
Accessing resource 4
Accessing resource 6
Accessing resource 5
訪問資源 6
訪問資源 4
訪問資源 5
Accessing resource 7
訪問資源 7
Accessing resource 8
訪問資源 8
Accessing resource 9
訪問資源 9