Open8

Swift Concurrency

MarinaMarina

非同期処理と同期処理の違い

非同期処理とは?

あるタスクで処理を実行中に別のタスクが他の処理を実行することを非同期処理という。あるタスクの処理が完了するのを待つ必要がない。
料理で例えると添付画像のようにAさんが目玉焼きを作っている間に、助っ人のBさんが野菜を切る。

同期処理とは?

プログラムで記述されている順番で処理が実行されることを同期処理という。タスクの処理が完了するのを待つ必要がある。
料理で例えると添付画像のようにAさんがひとりで目玉焼きを作り終えてから野菜を切る。

MarinaMarina

非同期処理の定義と呼び出し方

async

asyncは関数やメソッドを非同期処理として実行することを示す。

func listPhotos(inGallery name: String) async -> [String] {
    let result = // ... some asynchronous networking code ...
    return result
}

await

非同期なメソッドを呼び出す際、そのメソッドがreturnするまで処理はsuspendされる。そのため非同期処理を行いたい時は、suspendの可能性がある呼び出しの前にawaitを記載する。

let photoNames = await listPhotos(inGallery: "Summer Vacation")
let sortedNames = photoNames.sorted()
let name = sortedNames[0]
let photo = await downloadPhoto(named: name)
show(photo)

awaitを使える場所は主に下記の3ヵ所

  • async関数・メソッド、プロパティのbody
  • @main 属性が付けられた型のmain()メソッドのbody
  • Task や Task.detachedのClosure内
MarinaMarina

並列での非同期処理の呼び出し方

async let

下記のコードでは写真をダウンロードする処理が非同期的に呼び出されているが、全てが非同期処理となるので結局1つの写真がダウンロード完了するまで次の写真のダウンロードを開始しない。
しかし、それぞれの写真に依存関係はなく、それぞれ個別にダウンロード可能である。

Before.swift
let firstPhoto = await downloadPhoto(named: photoNames[0])  // 1番目に実行される
let secondPhoto = await downloadPhoto(named: photoNames[1]) // 2番目に実行される
let thirdPhoto = await downloadPhoto(named: photoNames[2]) // 3番目に実行される

let photos = [firstPhoto, secondPhoto, thirdPhoto]
show(photos)

非同期処理を並列で呼び出す場合は定数の前にasyncを付与し、定数を使う際にawaitを記載する。

After.swift
async let firstPhoto = downloadPhoto(named: photoNames[0])
async let secondPhoto = downloadPhoto(named: photoNames[1])
async let thirdPhoto = downloadPhoto(named: photoNames[2])

let photos = await [firstPhoto, secondPhoto, thirdPhoto]
show(photos)

上記のコードでは3つのdownloadPhoto()の呼び出しが前のダウンロード完了を待たずに開始される。photosはfirstPhoto, secondPhoto, thirdPhotoの結果を用いるため、awaitを記載し、「3つの結果が返ってくるまで処理を停止する」ということを示す。

MarinaMarina

Tasks and Task Groups

前提

Tasks

プログラムの一部として非同期的に実行できる単位の処理

Structured Concurrency

  • Task間の親と子の関係によりSwiftがキャンセル処理をある程度行なってくれる

Unstructured Concurrency

  • 親Taskがなく、必要に応じて自由に使えるが、コンパイル時にエラーを検知できないのでコードの正しさについては自分で責任を負わないといけない

Task Group

Task GroupにはTaskを追加することが可能
→async - letと比較して優先順位とcancellation をよりコントロールできる

  • Task Group内のTaskは同じ親Taskに紐付いており、各Taskは子Taskを持つことができる
  • Task Group内の親子関係によりSwiftのコンパイル時にエラーを検知できる

Task Cancellation

Swift ConcurrencyはCooperative Cancellation Modelを使い、各タスクは実行時適切な場所で処理がキャンセルされたかをチェックし、下記の適切な方法で処理のキャンセルに応える。

  • CancellationError
  • nilを返す
  • 部分的に完了した処理を返す

Taskがキャンセルされたかどうかを知る方法

Task.checkCancellation()はタスクがキャンセルされていた場合、CancellationErrorを返す。
またTask.isCancelledを用いて自分でハンドリングすることも可能。
手動でcancelするにはTask.cancel()を呼ぶ。

MarinaMarina

Actors

同時に走るコード間で安全に情報を共有でき、classのように参照型である。

actor外からアクセスする場合は、プロパティやメソッドの前にawaitを記載する。Actorは可変な状態にアクセスできるtaskを1つしか許可していないため、他のタスクのコードが既にアクセスしていた場合、awaitを用いることで処理を停止する箇所を明示している。

MarinaMarina

Sendable Types

プログラムの一部で変数やプロパティなど可変な状態を含む箇所をConcurrency Domainと呼ぶ。
重複したアクセスから守ってくれないため、可変なデータ保有するがConcurrency Domain間では共有できないデータの種類も存在する。