💭

【翻訳】Unstructured Concurrency With Detached Tasks in Swift

2023/07/16に公開

非同期タスクを理解することは、この記事を読むための必要条件です。
非同期タスクを理解していない場合は、この記事シリーズから
Unstructured Concurrency With Detached Tasks in Swift
(https://www.andyibanez.com/posts/unstructured-concurrency-with-detached-tasks-in-swift/)
を読むことができます。

この記事シリーズを通して、async/await が何であるかを探ってきました。
また、async letとGroup Tasksによる構造化された並行処理についても足を踏み入れました。
時には、構造化された並行処理は、良いものではありますが、すべてのケースをカバーすることはできないので、非構造化された並行処理の存在に言及し、
非構造化タスクを起動するためにTask {} ブロックを使用する方法を探りました。

この記事では、Swift 5.5 によって提供された最も柔軟な方法である、
Detached Tasksを使って、非構造化並行処理を実装する最後の方法を探ります。

Detached Tasksの導入

新しいasync/awaitシステムで検討したすべての同時実行オプションの中で、
Detached Tasksは最も柔軟性があります。
どこからでも起動でき、ライフタイムがスコープされず、(Task.Handleを通して)手動でキャンセルして
待機させることができ、親タスクから何も継承しないタスクの一種です。
優先順位でもありません。それらはコンテクストから独立しています。

Detached tasksは、親タスクから完全に独立したタスクを実行する必要がある場合に便利です。
1つの例は、ネットワークから画像をダウンロードし、後でディスクにキャッシュすることです
(この例は、AppleがWWDC2021のExplore Structured Concurrency in Swift
https://developer.apple.com/videos/play/wwdc2021/10134/の講演で
 使用しています)。
一旦画像を手に入れたら、ダウンロードタスクのキャンセルがキャッシング操作のキャンセルを引き起こす理由はないので、キャッシング操作は切り離されたタスクで起こることができます。

func storeImageInDisk(image: UIImage) async {
    guard
        let imageData = image.pngData(),
        let cachesUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else { return }
    let imageUrl = cachesUrl.appendingPathComponent(UUID().uuidString)
    try? imageData.write(to: imageUrl)
}
func downloadImageAndMetadata(imageNumber: Int) async throws -> DetailedImage {
    let image = try await downloadImage(imageNumber: imageNumber)
    Task.detached(priority: .background) {
        await storeImageInDisk(image: image)
    }
    let metadata = try await downloadMetadata(for: imageNumber)
    return DetailedImage(image: image, metadata: metadata)
}

storeImageInDiskタスクを作成しました。
そして、downloadImageAndMetadataのTask.detached内でこのメソッドを呼び出します。
画像がダウンロードされた直後に、キャッシュを試みます。

Task{}を理解すれば、Task.detached {}を理解することができます。
detached taskを起動するときは、優先順位を指定できます。
私たちの場合は、高い優先度で終了する必要のあるユーザー・タスクではないので、
バックグラウンドを使っています。.userInitiatedは、ユーザーがそのタスクを気にかけていることを意味し、
高い優先度を持つ必要があります。

これらのタスクは構造化されていないので、
Task.detachedはいつでもTaskをキャンセルするために使えるTaskハンドルを返します。
Task.detachedはそれを起動したタスクから独立していますが、
その中で起動された他のすべてのタスクは依然としてTask.detachedに依存しています。
したがって、Task.detachedタスクをキャンセルすると、
Task.detachedの中で別のTask.detachedを実行する場合を除き、
その子タスクはすべてキャンセルされたとマークされます。

要約

Task.detachedは、Task {}を理解すればそれほど難しくありません。これらの動作はほとんど同じです。
主な違いは、Task.detachedは親コンテキストから何も継承しないということです。
どちらも手動でキャンセルできます。これらは、依存しないタスクをいつでも実行するのに適しています。

【翻訳元の記事】

Unstructured Concurrency With Detached Tasks in Swift
https://www.andyibanez.com/posts/unstructured-concurrency-with-detached-tasks-in-swift/

Discussion