如何异步调用async函数而不等待结果

问题描述 投票:0回答:2

假设我有以下功能。

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
2个回答
9
投票

此行为将根据上下文而改变。

  • 如果您从非隔离上下文调用此函数,则

    first
    second
    将在单独的线程上运行。在这种情况下,
    second
    任务实际上并不是在等待
    first
    任务,而是存在一场关于哪个任务先完成的竞赛。如果您在
    first
    任务中执行一些耗时的操作,并且您会看到
    second
    任务根本不等待,就可以说明这一点。

    这引入了

    first
    second
    之间的竞赛,并且您无法保证它们将运行哪个顺序。 (在我的测试中,大多数时候它在
    second
    之前运行
    first
    ,但偶尔仍然可以在
    first
    之前运行
    second
    。)

  • 但是,如果您从参与者隔离的上下文中调用此函数,则

    first
    will 等待
    second
    在运行之前屈服。

那么,问题是,你真的关心这两个任务以哪个顺序开始吗?如果是这样,您可以通过(显然)在调用

Task { await first() }
之后放置
second
来消除竞争。或者您只是想确保
second
不会等待
first
完成?在这种情况下,这已经是行为,无需更改您的代码。


你问:

如果

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

您可以使用

@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
的缓慢同步计算不在当前参与者(大概是主要参与者)上运行,我们需要一个“非结构化任务”。具体来说,我们想要一个“独立任务”,即不在当前参与者上运行的任务。正如 Swift 编程语言:并发:任务和任务组非结构化并发 部分所述:

要创建在当前参与者上运行的非结构化任务,请调用

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


0
投票
  1. 停止在游乐场中使用并发 - 最小的模拟器,更好的真实设备

  2. 您的代码可以在设备和模拟器上正常工作

打印:

second
first
  1. 我无法重现你的例子总是得到:先第二次打印

  2. 停止使用单个 Print 语句的并发。您无法保证多核设备的速度有多快。您可以做什么来调查它是如何工作的:

  • 使用 (for in ) 循环处理 100 或 1000 个元素并打印
  • 使用 Task.sleep 或 Thread.sleep (对于 GCD )至少停止主线程,看看你的第一个打印是否会先打印。

我的代码与 DispatchQueue.current.async { first() }

完全相同
override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    
    Task {
       await first()
    }
    second()
}

func first() async {
    for i in 0...1000 {
      print("🤍 \(i)")
    }
}

func second() {
    for i in 0...1000 {
      print("♥️ \(i)")
    }
}

结果将首先是所有黑色红心,然后是所有白色

如果你什么DispatchQueue.global.async。使用 Task.detached

© www.soinside.com 2019 - 2024. All rights reserved.