【问题标题】:Swift async let cancellation doesn't workSwift async let 取消不起作用
【发布时间】:2021-10-24 06:30:19
【问题描述】:

我对被取消的任务感到有些困惑。

概述:

  • checkCancellation 函数有 2 个子任务,一个任务运行 computeA,另一个运行 computeB。它们同时运行,computeB 会抛出错误。

怀疑:

  • 我预计子任务 computeA 会被取消,因为 computeB 抛出错误,但 computeA 从未被取消。
  • 是我的理解有误还是我遗漏了什么?
  • 或者这是一个错误?

注意:

  • 我正在使用 SwiftUI 项目(因为 Swift Playgrounds 不支持 async let
  • macOS Big Sur 11.5.2 (20G95)
  • Xcode Version 13.0 beta 5 (13A5212g)

输出:

A - started
B - going to throw
A - going to return, Task.isCancelled = false
error: infinity

并发函数定义:

import Foundation
import UIKit

enum ComputationError: Error {
    case infinity
}

fileprivate func computeA() async throws -> Int {
    print("A - started")
    await Task.sleep(2 * 100_000_000)
    print("A - going to return, Task.isCancelled = \(Task.isCancelled)") //I expected Task.isCancelled to be true
    return 25
}

fileprivate func computeB() async throws -> Int {
    print("B - going to throw")
    throw ComputationError.infinity
}

func checkCancellation() async throws {
    async let a = computeA()
    async let b = computeB()
    
    let c = try await a + b
    print("c = \(c)")
}

调用并发函数

struct ContentView: View {
    var body: some View {
        Button("check cancellation") {
            Task {
                do {
                    try await checkCancellation()
                    print("normal exit")
                } catch {
                    print("error: \(error)")
                }
            }
        }
    }
}

观察:

  • 当我将代码更改为let c = try await b + a

输出:

A - started
B - going to throw
A - going to return, Task.isCancelled = true
error: infinity

怀疑:

我仍然不确定我是否理解原始代码中这种行为的原因

【问题讨论】:

  • 检查取消是任务的责任。您的“睡眠”不是“可取消的”,这意味着它不会检查任何“取消点”,因此它将一直运行到完成。请阅读Task Cancellation。 ;)
  • 另外,请注意在调用静态任务函数时,例如Task.isCancelled,您检查执行此语句的任务,即:Task { ...; print(Task.isCancelled) }
  • @CouchDeveloper 我同意由实现来检查取消并采取适当的措施,但是我的问题是为什么 Task.isCancelled = false 在输出中即使 computeB 抛出错误,理想情况下它应该有使正在进行的子任务被标记为已取消,这不会发生
  • @CouchDeveloper 根据developer.apple.com/videos/play/wwdc2021/10134,一旦其中一个子任务抛出错误,就会导致其他子任务被标记为已取消。在我的情况下,另一个子任务没有被标记为已取消。那是疑问/问题

标签: swift async-await concurrency swift-concurrency


【解决方案1】:

我预计子任务 computeA 会被取消,因为 computeB 抛出错误,但 computeA 从未被取消。是我的理解有误还是我遗漏了什么?

是的,是的。你的理解是错误的,你错过了什么。

视频中的评论与在任务组中创建的子任务有关。实际发生的是,当错误从子任务渗透到任务组时,任务组在所有其他子任务上调用 cancel

但是正在使用async let进行测试。没有任务组。所以没有人“在那里”取消另一个子任务。事实上,如果您仔细观看视频,您会发现它正确地告诉了您会发生什么:

  • 如果父任务被取消,子任务会自动取消。

  • 如果async let 任务在第二个async let 任务甚至被初始化之前抛出,那么第二个任务“天生就被取消”。 还在中等待(视频对此很清楚,而且是正确的);如果它没有通过中止来响应取消,它将继续完成。

这里的重点是父任务必须以良好的顺序结束。在它的孩子以一种或另一种方式完成之前,它不会结束。 (同样,视频对此非常清楚。)如果这意味着自动等待任务完成,尽管抛出,那么这就是会发生的事情。

另一个有趣的事情是,对于async let,你稍后会说try await,如果子任务抛出,你甚至不会听到它(在这方面什么都不会发生),直到你到达try await 发生的点。换句话说,投掷只能渗透到您实际说出try 的程度;没有别的意义了。

【讨论】:

    猜你喜欢
    • 2017-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-13
    相关资源
    最近更新 更多