【Swift】DispatchSemaphoreの使い方!非同期処理を待機する方法

この記事からわかること
- SwiftのDispatchSemaphoreの使い方
- 非同期処理を待機して実行する方法
- タイムアウトを設けるには?
index
[open]
\ アプリをリリースしました /
環境
- Xcode:15.0.1
- iOS:17.1
- Swift:5.9
- macOS:Sonoma 14.1
DispatchSemaphoreとは?
DispatchSemaphore
は複数のスレッドから操作される非同期処理を一元管理して同時にリソースを参照しないようにすることができるクラスです。仕組みとしては内部的にカウンターを保持しており、そのカウントが0の時は他の非同期処理は実行しないようにします。
DispatchSemaphore
クラスをインスタンス化する際に同時に実行できる非同期処理数を指定します。
// セマフォの初期カウントを1に設定
let semaphore = DispatchSemaphore(value: 1)
そして非同期処理を開始する際にwait
メソッドを実行し、非同期処理を終了する際にsignal
メソッドを実行します。wait
メソッドを実行すると内部カウンターが-1
され、signal
メソッドを実行すると内部カウンターが+1
されます。
semaphore.wait() // -1
// 非同期処理
semaphore.signal() // +1
カウンターが0になっている間は他の非同期処理を開始することができません。そのため非同期処理が終了したタイミングで適切にカウンターを戻さないと動作しなくなってしまうので注意してください。
実装サンプル
例えば以下のような非同期処理が2つあるとします。
print("開始")
DispatchQueue.global().async {
print("Task 1 start" )
sleep(4)
print("Task 1 end")
}
DispatchQueue.global().async {
print("Task 2 start")
sleep(2)
print("Task 2 end")
}
これをそのまま実行するとそれぞれの非同期処理は並列に動作するので出力は以下のようになります。
出力
開始
Task 1 start
Task 2 start
Task 2 end // 開始から2秒後
Task 1 end // 開始から4秒後
DispatchSemaphore
を導入してみると2つの非同期処理は直列に実行されるようになります。
// セマフォの初期カウントを1に設定
let semaphore = DispatchSemaphore(value: 1)
print("開始")
DispatchQueue.global().async {
// セマフォを待機
semaphore.wait()
print("Task 1 start" )
sleep(4)
print("Task 1 end")
// セマフォのシグナル
semaphore.signal()
}
DispatchQueue.global().async {
// セマフォを待機
semaphore.wait()
print("Task 2 start")
sleep(2)
print("Task 2 end")
// セマフォのシグナル
semaphore.signal()
}
出力
開始
Task 1 start
Task 1 end // 開始から4秒後
Task 2 start
Task 2 end // 開始から6秒後
タイムアウトを設ける
DispatchSemaphore
にはタイムアウトを設けることが可能です。このタイムアウトは指定した秒数の間非同期処理が開始にならなかった場合にタイムアウトを検出できます。
let result = semaphore.wait(timeout: .now() + 5) // 5秒間のタイムアウトを設ける
if result == .timedOut {
print("Timeout occurred")
} else {
// セマフォのシグナル
semaphore.signal()
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。