質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

339閲覧

ネストしているTaskすべてをキャンセルしたい

DeepSea

総合スコア4

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2022/09/26 18:15

前提

ModalViewController表示時に無限ループするTask(MainTask)が動作し、MainTaskは更に無限ループするTask(ChildTask)を動作させます。
ModalViewControllerではMainTaskをメンバ変数に保持し、deinit時にMainTaskをキャンセルします。

実現したいこと

ModalViewControllerをdismissした際deinitが走り、MainTaskのカウントが停止します。
しかし、ChildTaskのカウントは動作し続けます。
ModalViewControllerが認知及びキャンセルの制御を行うのはMainTaskのみとした場合、ChildTaskをMainTaskのキャンセルと連動させる方法はございますでしょうか。

該当のソースコード

swift

1class ModalViewController: UIViewController { 2 3 private var task: Task<(), Error>? 4 5 private var count = 0 6 7 deinit { 8 print("ModalViewController deinit") 9 task?.cancel() 10 } 11 12 override func viewDidLoad() { 13 super.viewDidLoad() 14 15 task = Task.detached { [weak self] in 16 Task { 17 var count = 0 18 while true { 19 try await Task.sleep(nanoseconds: NSEC_PER_SEC) 20 count += 1 21 print("ChildTask.count: \(count)") 22 } 23 } 24 25 while true { 26 guard let self = self else { return } 27 try await Task.sleep(nanoseconds: NSEC_PER_SEC) 28 await self.countIncrement() 29 print(await "MainTask.count: \(self.count)") 30 } 31 } 32 } 33 34 func countIncrement() { 35 count += 1 36 } 37 38 @IBAction func onButtonTap(_ sender: Any) { 39 dismiss(animated: true) 40 } 41}

試したこと

Task.isCancelled判定やTask.checkCancellation()をChildTask中に追加しましたが、trueになりませんでした。

補足情報(FW/ツールのバージョンなど)

  • Xcode14.0

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2022/09/28 13:17

> ModalViewControllerが認知及びキャンセルの制御を行うのはMainTaskのみとした場合、ChildTaskをMainTaskのキャンセルと連動させる方法はございますでしょうか。 下に引用した記事の内容を見ますと、 「MainTaskのみとした場合、」だとちょっと難しいのでしょうか? > タスクのキャンセル > 構造化プログラミングでは、外側の処理が完了するときには必ず内側の処理も完了している、というルールがありました。これは構造化された並列性における親タスクと子タスクの関係においても同じです。 > ...省略... > 構造化 "されていない" 並行性 > Swift に導入される構造化された並行性は、複数の並行処理を正しく効率的に管理するための優れた概念ですが、ときには構造化 "されていない" 並行性が必要な場合もあります。その理由として "Explore structured concurrency in Swift" セッションで挙げられているのは、ひとつは非同期的でないコンテキストから非同期的な処理を開始したい場合です。もうひとつは、構造化された並行性では子タスクのライフサイクルが親タスクを越えられないのに対して、親タスクのライフサイクルを越えて実行され続けるような処理を行いたい場合です。 > ...省略... > また、構造化されていないタスクが必要となる理由の一つである、親のライフサイクルに縛られないという性質も持っています。この性質は、逆に言えばタスクのキャンセルや完了の管理を Swift ランタイム側に任せられず、自前で管理しないといけないということを意味します。 > https://zenn.dev/akkyie/articles/swift-concurrency --- > Task.isCancelled判定やTask.checkCancellation()をChildTask中に追加しましたが、trueになりませんでした。 こちらにつきましては、 「構造化された並行性」で使えるものみたいですので、 「構造化 "されていない" 並行性」では使えないみたいですね・・ --- 質問欄の該当のソースコードですと、 アプリとして達成したい目的があまり見えてこない気もしますので(質問用のコードのため?)、 もう少し具体的なコードにすると回答をしてくれる人がいるかもしれません(別の案なども含め?)。
DeepSea

2022/09/28 13:59

ご検討いただき、ありがとうございます。 引用いただいた情報も大変勉強になりました。 構造化されていない場合、何かしらの合図でタスクを止める仕組み自体を作る必要があると理解しました。 コードにつきまして、タスクの振る舞いを確認するために興味本位で作成したものである為、これといった目的等はございません。 引き続き構造化されていないタスクに中断シグナルを送る方法について調べていこうと思います。
退会済みユーザー

退会済みユーザー

2022/09/28 23:20

コメントありがとうございます。 > コードにつきまして、タスクの振る舞いを確認するために興味本位で作成したものである為、これといった目的等はございません。 そうだったのですね。 業務的な理由があるものと思ってしまいました・・・ --- * 「ModalViewControllerが認知及びキャンセルの制御を行うのはMainTaskのみ」(質問欄) * 「タスクのキャンセルや完了の管理を Swift ランタイム側に任せられず、自前で管理しないといけない」(記事の引用) これらは矛盾するように思いまして、 単純に考えると、 ChildTaskもModalViewControllerに保持するようにして、 deinitで一緒にキャンセルする感じになるのかなと思いました。 *業務的な理由などがないのでしたら1点目の方を変更する感じになるでしょうか
guest

回答1

0

ベストアンサー

質問に掲載されているコードをコピペして動きを調べてみたところ、deinitでMainTaskのキャンセルを実施する前に、MainTaskのループ内の処理で guard let self = self else { return } により、selfがnilになったことを検知してメインタスクを終了していました。

なので、この処理でreturnする前にChildTaskをキャンセルすれば、MainTaskの終了時にChildTaskをキャンセルできると思います。

MainTaskでselfをキャプチャしない場合は、deinitでMainTaskのキャンセルが実施されるので、try awaitのエラーをcatchしてcancel処理を入れれば、MainTask終了時にChildTaskをキャンセルできると思います。

Taskがどのように動作しているか、printを入れて詳しく調べることをお勧めします。

(参考コード)

swift

1class ModalViewController: UIViewController { 2 3 private var task: Task<(), Error>? 4 5 private var count = 0 6 7 deinit { 8 print("ModalViewController deinit") 9 task?.cancel() 10 } 11 12 override func viewDidLoad() { 13 super.viewDidLoad() 14 15 task = Task.detached { [weak self] in 16 let childTask = Task { 17 var count = 0 18 while true { 19 try await Task.sleep(nanoseconds: NSEC_PER_SEC) 20 count += 1 21 print("ChildTask.count: \(count)") 22 } 23 } 24 25 while true { 26 guard let self = self else { 27 print("self is nil") 28 childTask.cancel() 29 break 30 } 31 do { 32 print("main task sleep await start") 33 try await Task.sleep(nanoseconds: NSEC_PER_SEC) 34 print("main task sleep await end") 35 await self.countIncrement() 36 print(await "MainTask.count: \(self.count)") 37 } catch { 38 if Task.isCancelled { 39 print("main task cancelled") 40 childTask.cancel() 41 break 42 } else { 43 print(error) 44 } 45 } 46 } 47 } 48 } 49 50 func countIncrement() { 51 count += 1 52 } 53 54 @IBAction func onButtonTap(_ sender: Any) { 55 dismiss(animated: true) 56 } 57}

投稿2022/10/02 06:06

TakeOne

総合スコア6299

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

DeepSea

2022/10/02 06:14

ご検討いただき、ありがとうございます。 ご指摘通りの方法で希望の動作となる事が確認できました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問