WWDC21で、AppleはSwift 5.5を発表した。現在はベータ版が使用可能だ。新機能の中で、最も待ち望まれていたもののひとつが、async/await
とアクタを使用した並行処理サポートの改善だ。
非同期関数は、Swiftの並行処理コードをより書きやすく、理解しやすくするためのものだ、とAppleは述べている。これまでのSwiftでは、非同期操作の処理にクロージャと完了ハンドラ(completion handlers)を使用していた。よく知られているように、このアプローチは、非同期操作の数が増えたり、コントロールフローが複雑になったりすることによって、容易に"Callback Hell"と呼ばれる状況に陥る。
Swiftの非同期関数は、代わりにコルーチンの使用を可能にする。
関数を非同期にオプトインできるようにすることで、通常のコントロールフローメカニズムを使って、非同期操作に関する複雑なロジックを構成できるようになります。非同期関数を適切なクロージャとステートマシンのセットに変換する作業は、コンパイラが実施してくれます。
以下のコードスニペットは、同期関数と同じようにasync
関数を宣言し、定義することが可能である、ということを示している。
func loadWebResource(_ path: String) async throws -> Resource
func decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image
func dewarpAndCleanupImage(_ i : Image) async throws -> Image
func processImageData() async throws -> Image {
let dataResource = try await loadWebResource("dataprofile.txt")
let imageResource = try await loadWebResource("imagedata.dat")
let imageTmp = try await decodeImage(dataResource, imageResource)
let imageResult = try await dewarpAndCleanupImage(imageTmp)
return imageResult
}
非同期関数は並列処理の管理を大きく簡略化する反面、デッドロックやステート破壊の可能性がなくなる訳ではない。特に注意が必要なのは、async関数で導入されたSuspension Pointだ。Suspension Pointに達すると、関数はそのスレッドを放棄する。これが発生するのは、例えば、異なる実行コンテストに関連付けられた非同期関数を呼び出した場合だ。デッドロックやデータ破損のリスクを避けるため、非同期関数では、自身のスレッドをブロックする関数の呼び出しを回避する必要がある。
例えば、ミューテックスの獲得は、並行動作するスレッドのいずれかがミューテックスを開放するまで待つだけなので、受容される場合もありますが、デッドロックや人為的なスケーラビリティの問題を回避するように注意する必要があります。対照的に、条件変数を待機する処理は、その変数に信号を送信する他の処理がスケジュールされるまでブロックされる可能性があるため、推奨に強く反するパターンです。
この機能の進化で興味深いのは、await
式を使用した完了ハンドラを用いることで、Objective-C APIの非同期呼び出しが可能になる点だ。
一方のアクタ(actor)は、変更可能(mutable)なステートへの安全なアクセスを実現するために、async
とawait
の上に構築された抽象概念である。簡単に言うと、ある種のステートをカプセル化して、安全にアクセス可能な一連のメソッドを提供するものがアクタだ。
クラスとは異なり、アクタでは、可変ステートに同時アクセス可能なタスクはひとつに限定されます。これによって、アクタの同じインスタンスにアクセスするタスク間での安全性が保証されるのです。
Swiftアクタの例を示す。
actor TemperatureLogger {
let label: String
var measurements: [Int]
private(set) var max: Int
init(label: String, measurement: Int) {
self.label = label
self.measurements = [measurement]
self.max = measurement
}
}
アクタのメソッドは、アクタ内からは同期あるいは非同期の区別なく使用できるが、アクタの外部からステートを読み出す場合には、コンパイラによって非同期操作の使用が強要される。
Swiftの並列処理の内部動作や、Grand Central Dispatchとの違い、パフォーマンスを考慮した並行処理をSwiftで記述する方法などに興味があるならば、Apple WWDCのセッション "Swift concurrency: Behind the scenes"は必見である。
Swift 5.5は現在、Xcode 13ベータ版の一部として提供されており、Apple Developer Webサイトからダウンロードが可能だ。