Swift中,如果不是Awaited,则让Task抛出Cancellation Exception

bhmjp9jg  于 2023-11-16  发布在  Swift
关注(0)|答案(3)|浏览(111)

我是Swift并发的新手,试图理解Task -> ChildTask的关系。
我在这里创建了两个任务

  • 父任务:
  • (1)从MainThread调用Actor中的test()方法。
  • (3.2)Actor在后台线程中调用test()方法.
  • (2)然后将控制返回到主线程上的父任务
  • 子任务:
  • (3)在MainThread中调用
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
  • 我这里的问题是,为什么任务被取消时,我不等待*c_let test()方法
093gszye

093gszye1#

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

Task {
    async let _ = ViewModel().test()
    Task {
        do {
            try await Task.sleep(for: .milliseconds(500))
        } catch {
            print(error)
        }
    }
}

个字符
你似乎认为等待0.5秒的任务是运行ViewModel().test()的任务的“子”。这是不正确的。通过使用Task { ... },你启动了一个 * 顶级任务 *,它不是任何东西的子任务。你没有await这个任务,所以外部Task所做的就是启动ViewModel().test()的任务,启动另一个顶级任务(不等待它),然后结束。
要真正等待顶级任务,您可以执行以下操作:

await Task {
    ...
}.value


但如果你只想等待一段时间,你根本不需要一个顶级任务。直接写就行了:

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


现在“Task.sleep“任务是您创建的单个顶层任务的子任务。我假设您从现在开始进行了上述更改。
不像await实际上是等待,async let是与它周围的代码并行运行的。请参阅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

taor4pac

taor4pac2#

大家问:
我的问题是,为什么我没有等待async_let test()方法,任务就被取消了
请参阅SE-0317 - async let - implicit async let waiting,其中指出如果您无法执行await,则会“隐含地取消并等待”。
请考虑:

func go() async { 
    async let f = foo()
    print("\(#function) nevermind...")
    // implicitly: cancels f
    // implicitly: await f
}

func foo() async throws {
    do {
        try await Task.sleep(for: .seconds(1))
        print("\(#function) done")              // because async let will implicitly cancel this, and because `Task.sleep` detects cancellation and throws `CancellationError`, we won’t get here
    } catch {
        print("\(#function) error \(error)")    // when async let is implicitly canceled, `CancellationError` will be caught here
        throw error                             // but rethrown here
    }
}

字符串
如果你运行这个,因为Task.sleep是非阻塞的,并且处理取消,所以一旦x1m2 n1福尔斯范围,foo就会被立即取消,它也会被等待,但是因为取消是立即处理的,所以很难观察到。
所以,让我们考虑这个例子:

func go() async { 
    async let f = foo()
    async let b = bar()
    print("\(#function) nevermind...")
    // implicitly: cancels f
    // implicitly: cancels b
    // implicitly: await f
    // implicitly: await b
}

func foo() async throws {…}                     // same as above

func bar() async throws {
    do {
        Thread.sleep(forTimeInterval: 1)        // never Thread.sleep, but for illustrative purposes, we want something that does not directly respond to cancellation 
        try Task.checkCancellation()            // we'll manually check for cancelation when `Thread.sleep` is done
        print("\(#function) done")              // because async let is implicitly canceled and `checkCancellation` will detect that, we won’t get here
    } catch {
        print("\(#function) error \(error)")    // we will catch `CancellationError` here
        throw error                             // and rethrow it here
    }
}


现在,在本例中,您将再次看到隐式取消,并等待foobar。(即,不立即处理取消,而是仅在Thread.sleep完成之后检测到取消,并且我们手动地进行checkCancellation),go函数将await完成bar。“
但是,如果我们将bar更改为主动响应取消,即改用Task.sleep(如foo),那么现在两者都将被快速取消并等待。

bis0qfac

bis0qfac3#

正如@Sweeper所说,父任务不会等待子任务完成,它们只共享任务的上下文(无论是要取消的任务还是启动它的线程)
我在任务中添加了一些defer {logs},发现父任务在取消之前被释放了prints 5.x。因此,task_let_task也被取消了.

class ViewController: UIViewController {
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        print("1 ")
        Task {
            async let x = ViewModel().test()        // Throws 3.3 CancellationError()
            // await ViewModel().test()             // Does not cancel task and works fine
            
            print("2 ")
            Task {
                do {
                    print("4.0 ")
                    Thread.sleep(forTimeInterval: 1)
                    print("4.1 ")
                } catch {
                    print("4.2 ")
                    print(error)
                }
                
                defer {
                    print("4.X")
                }
            }
            
            defer {
                print("5.X")
            }
        }
    }
    
    actor ViewModel {
        func test() async {
            print("3.1 ")
            
            do {
                let images = try await downloadImage()
                print("3.2 ")
            } catch {
                print("3.3 \(error)")
            }
            
            defer {
                print("3.X")
            }
        }
        
        func downloadImage() async throws -> UIImage {
            try Task.checkCancellation()
            await Thread.sleep(forTimeInterval: 1) // 1seconds
            return UIImage()
        }
    }
}

字符串

相关问题