はじめに
一定時間内に処理が来なければ異常としてタイムアウトする処理をDispatchQueueを使用して作成しました。
非同期処理はC言語で勉強していましたが、しっかりと理解できていなかったため復習もかねてswiftでの実装方法まとめていこうと思います。
目次
- 非同期処理とは
- DispatchQueueについて
- サンプルプログラム(DispatchQueue)
- Swift Concurrency(async/await)について
- サンプルプログラム(Concurrency)
- おわりに
- 参考にしたサイト
1.非同期処理
同期処理と非同期処理は、実現したい処理によって、使い分ける必要のあるプログラミング手法です。
それぞれについて説明します。
同期処理
同期処理はプログラムを順番に実行していく方法です。
A→B→Cの順番にプログラムをかいていたときはその順番にプログラムが実行されます。
Aの処理が終わるまではBの処理に進まず、Aの処理が終わってBの処理を飛ばしてCの処理を実行することはできません。
メリット:どの処理が実行されるかがわかりやすい
デメリット:完了しないと次の処理が実行されないため、同時に別のことを行えない
非同期処理
非同期処理は複数のプログラムを同時に実行できる方法です。
プログラムを実行している最中にその処理を止めることなく、別のプログラムを実行できます。
メリット:複数同時に処理を行えるため、複雑な制御が行える
デメリット:同期処理では発生しない問題が発生する(リソースの排他など)
今回はプログラムの実行が完了しなかったときに別の処理を実行したいため、同時実行可能な非同期処理を利用します。
2.DispatchQueueについて
DispatchQueueはメインスレッドまたはバックグラウンドスレッドでタスクの実行を順次または同時に管理するオブジェクトです。
Queueのため、FIFO(先入先出)方式で実行されます。
メインキュー
メインスレッドでタスクが実行される直列のキューのことです。
UIアプリを作成する場合、UIスレッドはここで管理されています。(UI処理はUIスレッドからしか実行できません。)
メインキューで同期処理を行うとデッドロックが発生するため、メインキューではsyncメソッドは使用できません。
グローバルキュー
システム全体で共有される並列のキューのことです。
非同期処理なのでタスク処理の順番は決まっておらず、早い順に処理されます。
6つの異なる優先度があります。優先度が高い順に表にまとめています。
優先度 | 使用内容 |
---|---|
userInteractive | 即時に実行されなければならない処理 |
userIntiated | ユーザーの入力を受けて実行される処理 |
default | 優先度が指定されていない時の処理 |
utility | すぐに結果が必要ないときの処理 |
background | バックアップなどの見えないところの処理 |
unspecified | 特に指定なし |
DispathSemaphore
今回、一定時間内に処理が行われない場合次の処理に進むプログラムを作成するのにセマフォを利用するのでDispathSemaphoreについて説明します。
DispatchSemaphoreはsignal()でセマフォ数を1増加させ、wait()でセマフォ数を1減少させます。
wait()の呼び出し後、カウントが0未満となると制御が戻り、処理が進みます。
3.サンプルプログラム(DispatchQueue)
DispatchQueueとDispatchSemaphoreを使って、処理が5秒以内に来た場合は成功、来なかった場合はタイムアウトと判断するプログラムを作成しました。
import Foundation
import Cocoa
let semaphore = DispatchSemaphore(value: 1)
DispatchQueue.global(qos: .userInitiated).async {
print("async start")
sleep(3) //一定時間内に行われなかったらタイムアウトしてほしい処理を入れる
print("async end")
semaphore.signal() //セマフォを+1して止めたところから処理を再開させる
}
print("start")
semaphore.wait() //セマフォを−1して処理を止める
switch semaphore.wait(timeout: .now() + 5.0) { //5秒以内に処理が行われるか確認する
case .success:
print("success")
case .timedOut:
print("timeout")
}
print("end")
コンソール出力結果は以下のようになります。
start
async start
async end
success
end
ここで、スリープしている個所を、sleep(10)に変えると以下の出力となることを確認できます。
start
async start
timeout
end
4.Swift Concurrency(async/await)
記事を作成するにあたってSwiftの非同期処理について調べていると、Swift Concurrencyという機能の存在を知りました。
Swift Concurrencyは、非同期処理や並行処理のコードを簡潔かつ安全に記述できる機能で、Swift5.5、ios15から使用できます。
メリットは同期的な処理と非同期的な処理をほぼ同じ見た目で書けるようになる点です。
関数にasyncを付けることで非同期的なコンテキストになります。
awaitはasyncな関数を呼び出すときにつけることで結果を待つことができます。
5.サンプル(Swift Concurrency)
3で作成したサンプルをSwift Concurrency(async/await)で作成しました。
今回は簡単にコンテキストを作成するためにTask.detachedを使用しています。
import Foundation
import Cocoa
let semaphore = DispatchSemaphore(value: 1)
func sample() async {
print("async start")
sleep(3) //一定時間内に行われなかったらタイムアウトしてほしい処理を入れる
print("async end")
semaphore.signal() //セマフォを+1して止めたところから処理を再開させる
}
Task.detached{ //非同期で実行
await sample() //awaitを付けて呼び出すことで結果を待つ
}
print("start")
semaphore.wait() //セマフォを−1して処理を止める
switch semaphore.wait(timeout: .now() + 5.0) { //5秒以内に処理が行われるか確認する
case .success:
print("success")
case .timedOut:
print("timeout")
}
print("end")
出力結果などの動作は前述のDispatchQueueと同じになります。
6. おわりに
非同期処理は必要に応じて適切に使用する必要があることが理解できました。
DispatchQueue以外にも、Swift Concurrencyを使えば簡易に記述できると学ぶことができたので良かったです。
7.参考にしたサイト
https://tech.amefure.com/swift-dispatchqueue
https://developer.apple.com/documentation/dispatch/dispatchqos/qosclass
https://zenn.dev/akkyie/articles/swift-concurrency#async%2Fawait
https://zenn.dev/koher/articles/swift6-concurrency
https://blog.personal-factory.com/2022/01/23/how-to-use-async-await-since-swift5_5/
https://wa3.i-3-i.info/word13357.html
https://scior.hatenablog.com/entry/2019/09/11/231626