【问题标题】:How to call async function asynchronously without awaiting for the result如何在不等待结果的情况下异步调用异步函数
【发布时间】:2021-12-19 02:16:41
【问题描述】:

假设我有以下功能。

func first() async {
    print("first")
}

func second() {
   print("second")
}

func main() {
   Task {
      await first()
   }
   second()
}

main()

尽管将first 函数标记为异步没有任何意义,因为它没有异步工作,但仍有可能......

我期待即使第一个函数正在等待,它也会被异步调用。

但实际上输出是

first 
second

我如何调用第一个函数来异步模仿 GCD 的变体:

DispatchQueue.current.async { first() }
second()

【问题讨论】:

    标签: swift asynchronous async-await grand-central-dispatch swift5.5


    【解决方案1】:

    second 任务未等待在单独线程上运行的first 任务完成。如果您在 first 任务中执行了一些耗时的操作,并且您将看到 second 任务根本没有等待,则可以说明这一点。

    使用Task { … } 更类似于DispatchQueue.global().async { … } 而不是DispatchQueue.main.async { … }。它在一个单独的线程上启动 first。这引入了firstsecond 之间的竞争,您无法保证它们将运行哪个顺序。 (在我的测试中,它大部分时间在first 之前运行second,但它仍然可以偶尔在second 之前运行first。)

    所以,问题是,你真的关心这两个任务从哪个顺序开始吗?如果是这样,您可以通过(显然)在调用second 之后放置Task { await first() } 来消除竞争。或者您只是想确保second 不会等待first 完成?在这种情况下,这已经是行为,不需要更改您的代码。


    你问:

    如果await first() 需要在与second() 相同的队列上运行,但要异步运行。 …我只是在想 [如果它在后台线程上运行] 将意味着由于 UI 更新而不是来自主线程而导致崩溃。

    您可以使用@MainActor 标记更新 UI 的例程,这将导致它在主线程上运行。但请注意,不要将此限定符与耗时任务本身一起使用(因为您不想阻塞主线程),而是将耗时操作与UI更新解耦,并将后者标记为@987654346 @。

    例如,这是一个手动异步计算 π 并在计算完成后更新 UI 的示例:

    func startCalculation() {
        Task {
            let pi = await calculatePi()
            updateWithResults(pi)
        }
        updateThatCalculationIsUnderway() // this really should go before the Task to eliminate any races, but just to illustrate that this second routine really does not wait
    }
    
    // deliberately inefficient calculation of pi
    
    func calculatePi() async -> Double {
        await Task.detached {
            var value: Double = 0
            var denominator: Double = 1
            var sign: Double = 1
            var increment: Double = 0
    
            repeat {
                increment = 4 / denominator
                value += sign * 4 / denominator
                denominator += 2
                sign *= -1
            } while increment > 0.000000001
    
            return value
        }.value
    }
    
    func updateThatCalculationIsUnderway() {
        statusLabel.text = "Calculating π"
    }
    
    @MainActor
    func updateWithResults(_ value: Double) {
        statusLabel.text = "Done"
        resultLabel.text = formatter.string(for: value)
    }
    

    注意:为了确保calculatePi 的这种慢速同步计算不会在当前参与者(可能是主要参与者)上运行,我们需要一个“非结构化任务”。具体来说,我们想要一个“分离任务”,即不在当前参与者上运行的任务。正如The Swift Programming Language: Concurrency: Tasks and Task Groups非结构化并发部分所说:

    要创建在当前参与者上运行的非结构化任务,请调用 Task.init(priority:operation:) 初始化程序。要创建不属于当前参与者的非结构化任务(更具体地说称为分离任务),请调用 Task.detached(priority:operation:) 类方法。

    【讨论】:

    • 你说得对,我在print("first")first 之前在first 函数中添加了1 秒延迟,在控制台中second 之后记录了first。如果await first() 需要在与second() 相同的队列上运行但异步,这是否可以使用Task api,或者需要GCD 来实现?
    • 我只是在想Task { ... } 是否比 UI 代码更接近 DispatchQueue.global().async { first() },这意味着由于 UI 更新而不是来自主线程而导致崩溃。
    • 回答我自己的评论,将@MainActor 属性添加到first() 函数确保它将被安排在主线程上。
    • 是的,但要小心。将耗时的东西与 UI 更新分离。 UI 更新函数将带有 @MainActor 限定符,但耗时的函数 first 不应该。
    • @VladislavBrylinskiy 你想通过在同一个队列上运行first()second() 来实现什么?把它们放在演员里面能解决你的问题吗?
    猜你喜欢
    • 2016-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-20
    • 1970-01-01
    • 2020-06-03
    相关资源
    最近更新 更多