首页 > 解决方案 > 如何在不等待结果的情况下异步调用异步函数

问题描述

假设我有以下功能。

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

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

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

main()

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

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

但实际上输出是

first 
second

我将如何调用 first 函数异步模仿 GCD 的变体:

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

标签: swiftasynchronousasync-awaitgrand-central-dispatchswift5.5

解决方案


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

usingTask { … }更类似于DispatchQueue.global().async { … }to DispatchQueue.main.async { … }。它从first一个单独的线程开始。这引入了first和之间的竞赛second,您无法保证他们将按照哪个顺序运行。(在我的测试中,它大部分时间都在之前运行,但它仍然可以偶尔在second之前运行。)firstfirstsecond

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


您问:

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

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

例如,这是一个手动异步计算 π 并在完成后更新 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不在当前参与者(可能是主要参与者)上运行,我们需要一个“非结构化任务”。具体来说,我们想要一个“分离任务”,即不在当前actor上运行的任务。正如The Swift Programming Language: Concurrency: Tasks and Task Groups的非结构化并发部分所说:

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


推荐阅读