如何在不等待结果的情况下异步调用异步函数

How to call async function asynchronously without awaiting for the result

假设我有以下功能。

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()

second 任务未等待 first 任务完成,后者 运行 在单独的线程上完成。如果您在 first 任务中做了一些耗时的事情并且您会看到 second 任务根本没有等待,这可以说明这一点。

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

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


您问的是:

What if await first() needs to be run on the same queue as second() but asynchronously. … I am just thinking [that if it runs on background thread that it] would mean crashes due to updates of UI not from the main thread.

您可以将例程标记为用 @MainActor 更新 UI,这将导致它在主线程上变为 运行。但请注意,不要将此限定符与耗时任务本身一起使用(因为您不想阻塞主线程),而是将耗时操作与 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 非结构化并发 部分所述:

To create an unstructured task that runs on the current actor, call the Task.init(priority:operation:) initializer. To create an unstructured task that’s not part of the current actor, known more specifically as a detached task, call the Task.detached(priority:operation:) class method.