ios - Swift async 让任务在未等待时抛出取消异常

标签 ios swift async-await concurrency

我是 Swift Concurrency 的新手,并试图了解 Task -> ChildTask 关系。

我在这里创建了两个任务

  • 家长任务:
    • (1)从MainThread调用Actor中的test()方法。
    • (3.2)Actor调用后台线程中的test()方法...
    • (2)然后将控制权返回给MainThread上的父任务
  • 子任务:
    • (3)在主线程中运行
class MyViewController: ViewController {
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        print("1 \(Thread.current.isMainThread)")
        Task {
            
            async let _ = ViewModel().test()        // Throws 3.3 CancellationError()
            // await ViewModel().test()             // Does not cancel task and works fine
            
            
            print("2 \(Thread.current.isMainThread)")
            Task {
                do {
                    Thread.sleep(forTimeInterval: 0.5)
                    print("4.1 \(Thread.current.isMainThread)")
                } catch {
                    print("4.2 \(Thread.current.isMainThread)")
                    print(error)
                }
            }
        }
    }
    
    actor ViewModel {
        func test() async {
            print("3.1 \(Thread.current.isMainThread)")
            
            do {
                print("3.2 \(Thread.current.isMainThread)")
                let images = try await downloadImage()
            } catch {
                print("3.3 \(error)")
            }
        }
        
        func downloadImage() async throws -> UIImage {
            try Task.checkCancellation()
            await Thread.sleep(forTimeInterval: 1) // 1seconds
            return UIImage()
        }
    }
}
async let _ = ViewModel().test()        // Throws 3.3 CancellationError()
// await ViewModel().test()
1 true
2 true
3.1 false
3.2 false
3.3 CancellationError()
4.1 true
// async let _ = ViewModel().test()        
await ViewModel().test()             // Does not cancel task and works 
1 true
3.1 false
3.2 false 
2 true 
4.1 true

我的问题是,为什么当我不等待时任务被取消 async_let test() 方法

最佳答案

您不应在 async 上下文中使用 Thread.currentThread.sleep。这在 Swift 6 中将是一个错误。让我们删除这些并只考虑以下代码:

Task {
    async let _ = ViewModel().test()
    Task {
        do {
            try await Task.sleep(for: .milliseconds(500))
        } catch {
            print(error)
        }
    }
}
actor ViewModel {
    func test() async {
        do {
            let images = try await downloadImage()
            print("Finished!")
        } catch {
            print("Error: \(error)")
        }
    }
    
    func downloadImage() async throws -> UIImage {
        try Task.checkCancellation()
        try await Task.sleep(for: .seconds(1))
        return UIImage()
    }
}

您似乎认为等待 0.5 秒的任务是运行 ViewModel().test() 的任务的“子级”。这不是真的。通过使用Task { ... },您可以启动一个不属于任何子项的顶级任务。而且您不会等待此任务,因此所有外部Task所做的就是启动ViewModel().test()的任务,启动另一个顶级任务(不等待),然后结束。

要真正等待顶级任务,您可以执行以下操作:

await Task {
    ...
}.value

但是如果你只想等待一段时间,那么根本不需要顶级任务。直接写:

do {
    try await Task.sleep(for: .milliseconds(500))
} catch {
    print(error)
}

现在“Task.sleep”任务是您创建的单个顶级任务的子任务。我假设您从现在开始进行了上述更改。

与实际等待的 await 不同,async let 与其周围的代码并行运行。请参阅this section在 Swift 指南中有一个例子。如果您在某个时刻不 awaitlet 创建的变量(您甚至没有给它命名),那么没有人会等待它完成。

async let x = someAsyncThing()
anotherThing() // this line will not wait for someAsyncThing to finish
let result = await x // now we wait

因此没有人等待 ViewModel().test() 完成。并行启动 ViewModel().test() 后,您只需等待 0.5 秒,这不足以让 ViewModel().test() 完成。 0.5 秒后,顶级任务结束,运行 ViewModel().test() 的任务被取消,因为它是顶级任务的子任务。这解释了CancellationError

关于ios - Swift async 让任务在未等待时抛出取消异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77463784/

相关文章:

c# - 在 MVC 中使用异步操作更新 View

ios - 在 Interface Builder 中创建工具栏信息按钮

ios - 尝试解析 JSON 以在应用程序中显示图像

ios - 如何使用 Swift 播放本地视频?

ios - 通过在 Swift 中添加两个标签而不是标题来自定义导航栏

swift - 有没有办法获取类的变量和函数列表

c# - 异步等待性能?

c# - 在已知已完成的任务上调用 .Result 或 await 之间有区别吗?

ios - UITextField 在 CABasicAnimation 期间被裁剪,keyPath 为 bounds.size.width

ios - SIGVART 与单元 View 与标签