如何在swift中使用wait处理元组?

ttygqcqt  于 2023-02-28  发布在  Swift
关注(0)|答案(4)|浏览(155)

我试图确保我理解了await的行为,假设我们有以下函数:

func do() async {
  //code
}
func stuff() async {
  //code
}

以下语句将依次运行dostuff

await do()
await stuff()

但是下面的语句将并行运行dostuff,对吗?

await (do(), stuff())

我不知道如何检入Xcode,如果我的代码是并行运行还是顺序运行。

6l7fqoea

6l7fqoea1#

    • 简短答复:**

如果您希望并发执行,请使用async let模式或任务组。

    • 详细答案:**

你说:
但是下面的语句将并行运行do和stuff,对吗?

await (do(), stuff())

不,他们不会的。
以下经验最能说明这一点:

  • 使任务花费足够的时间,以便可以轻松地显示并发行为;以及
  • 使用仪器中的"兴趣点"仪器(例如,通过选择"时间刻画器"模板)以图形方式表示随时间变化的间期。

考虑下面的代码,使用元组方法:

import os.log

private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: .pointsOfInterest)

以及:

func example() async {
    let values = await (self.doSomething(), self.doSomethingElse())
    print(values)
}

func doSomething() async -> Int {
    spin(#function)
    return 1
}

func doSomethingElse() async -> Int {
    spin(#function)
    return 42
}

func spin(_ name: StaticString) {
    let id = OSSignpostID(log: log)
    os_signpost(.begin, log: log, name: name, signpostID: id, "begin")

    let start = CACurrentMediaTime()
    while CACurrentMediaTime() - start < 1 { }   // spin for one second

    os_signpost(.end, log: log, name: name, signpostID: id, "end")
}

这将产生一个图表,显示它不是同时发生的:

鉴于:

func example() async {
    async let foo = self.doSomething()
    async let bar = self.doSomethingElse()
    let values = await (foo, bar)
    print(values)
}

这会导致并发执行:

现在,在上面的例子中,我修改了函数,使它们返回值(因为这是使用元组有实际意义的唯一上下文)。
但如果它们不返回值,而您希望它们并行运行,则可以使用任务组:

func experiment() async {
    await withTaskGroup(of: Void.self) { group in
        group.addTask { await self.doSomething() }
        group.addTask { await self.doSomethingElse() }
    }
}

func doSomething() async {
    spin(#function)
}

func doSomethingElse() async {
    spin(#function)
}

这导致了它们并行运行的相同图形。
您也可以只创建Task示例,然后再创建await示例:

func experiment() async {
    async let task1 = Task { await doSomething() }
    async let task2 = Task { await doSomethingElse() }
    _ = await task1
    _ = await task2
}

但是,当编译时可能不知道创建的任务数量时,任务组提供了更大的灵活性。

dnph8jn4

dnph8jn42#

看起来,为了获得并发执行,每个函数必须有一个显式的async let

actor A {
    var t = 1
    func tt() -> Int {
        for i in 0 ... 1000000 {
            t += i
        }
        let s = t
        t = 1
        return s
    }
}
var a = A()
var b = A()
func go() {
    Task {
        var d = Date()
        await (a.tt(), b.tt())
        print("time=1",d.timeIntervalSinceNow)
        
        
        d = Date()
        await a.tt()
        await b.tt()
        print("time2=",d.timeIntervalSinceNow)
        
        d = Date()
        async let q = (a.tt(), b.tt())
        await q
        print("time3=",d.timeIntervalSinceNow)
        
        d = Date()
        async let q1 = a.tt()
        async let q2 = b.tt()
        await q1
        await q2
        print("time4=",d.timeIntervalSinceNow)
        
        d = Date()
        async let q3 = a.tt()
        async let q4 = b.tt()
        await (q3, q4)
        print("time5=",d.timeIntervalSinceNow)
    }
}

打印输出:

time1= -0.4335060119628906
time2= -0.435217022895813
time3= -0.4430699348449707
time4= -0.23430800437927246
time5= -0.23900198936462402
ztmd8pv5

ztmd8pv53#

基于gloo的回答,另一个测试使用睡眠作为耗时的工作。
环境

$ xcrun swift -version
swift-driver version: 1.62.15 Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51)
Target: arm64-apple-macosx13.0

$ xcodebuild -version
Xcode 14.2
Build version 14C18
// Executed in playground
import Foundation

actor A {
    func job(_ i: Int, _ d: Date) async throws -> Int {
        try await Task.sleep(nanoseconds: UInt64(2) * NSEC_PER_SEC) // the number 2 can be changed
        print("i=\(i)  time spent = \(-d.timeIntervalSinceNow)")
        return i
    }
}

var a = A()
var b = A()
var d = Date()

func go() {
    Task {
        do {
            // case 1 - non concurrent
            print("case 1")
            d = Date()
            try await (a.job(11, d), b.job(12, d))
            print("time spent case 1 = \(-d.timeIntervalSinceNow)")
            
            // case 2 - non concurrent
            print("case 2")
            d = Date()
            try await a.job(21, d)
            try await b.job(22, d)
            print("time spent case 2 = \(-d.timeIntervalSinceNow)")
            
            // case 3 - non concurrent
            print("case 3")
            d = Date()
            async let q3 = (a.job(31, d), b.job(32, d))
            try await q3
            print("time spent case 3 = \(-d.timeIntervalSinceNow)")
            
            // case 4 - concurrent, 41 or 42 may be printed first
            print("case 4")
            d = Date()
            async let q41 = a.job(41, d)
            async let q42 = b.job(42, d)
            try await q41
            try await q42
            print("time spent case 4 = \(-d.timeIntervalSinceNow)")
            
            // case 4.1 - concurrent, 411 or 412 may be printed first
            print("case 4.1")
            d = Date()
            async let q411 = a.job(411, d)
            async let q412 = b.job(412, d)
            try await q411
            print("case 4.1 mid") //
            try await q412
            print("time spent case 4.1 = \(-d.timeIntervalSinceNow)")
            
            // case 5 - concurrent, 51 or 52 may be printed first
            print("case 5")
            d = Date()
            async let q51 = a.job(51, d)
            async let q52 = b.job(52, d)
            try await (q51, q52)
            print("time spent case 5 = \(-d.timeIntervalSinceNow)")
            
            // case 6 - non concurrent
            print("case 6")
            d = Date()
            _ = (try await a.job(61, d), try await b.job(62, d))
            print("time spent case 6 = \(-d.timeIntervalSinceNow)")
        } catch {
            print("error=\(error)")
        }
    }
}
go()

典型输出:

case 1
i=11  time spent = 2.1285619735717773
i=12  time spent = 4.261919021606445
time spent case 1 = 4.262814998626709
case 2
i=21  time spent = 2.1167279481887817
i=22  time spent = 4.234173059463501
time spent case 2 = 4.234648942947388
case 3
i=31  time spent = 2.1281230449676514
i=32  time spent = 4.263576984405518
time spent case 3 = 4.264067053794861
case 4
i=42  time spent = 2.130499005317688
i=41  time spent = 2.130929946899414
time spent case 4 = 2.131192922592163
case 4.1
i=412  time spent = 2.133057951927185
i=411  time spent = 2.13319993019104
case 4.1 mid
time spent case 4.1 = 2.133283019065857
case 5
i=51  time spent = 2.132704973220825
i=52  time spent = 2.133439064025879
time spent case 5 = 2.1337080001831055
case 6
i=61  time spent = 2.13224995136261
i=62  time spent = 4.265568017959595
time spent case 6 = 4.266121029853821
bxfogqkk

bxfogqkk4#

不,它们不是并行执行的。
首先,

await (do(), stuff())

是的简写语法

(await do(), await stuff())

它是从左到右计算的,就像任何其他代码行一样。
现在,任何await调用都会挂起调用方的执行,如果被调用方挂起的话,这意味着只有在await do()完成之后,await stuff()才会继续执行。
await是一个挂起点(实际上是一个 * 可能的 * 挂起点),这意味着在await完成之前,执行不会继续(无论是异步还是同步方式,都无关紧要)。
我不知道如何检入Xcode,我的代码是并行运行还是顺序运行
除非你不信任编译器,否则你不需要这样做。从同一个async上下文(任务)派生多个await的唯一方法是通过async-letTaskGroup
但如果你真的想了解一下,出于学习的目的,其他人已经提供了一些有趣的方法,我只想留下我的方法,它与另一个已经提供的方法相似:

func delay(for interval: TimeInterval = 2.0, marker: StaticString = #function) async {
    print("\(marker) begin")
    try? await Task.sleep(nanoseconds: UInt64(interval * 1_000_000))
    print("\(marker) end")
}

func `do`() async {
    await delay()
}

func stuff() async {
    await delay()
}

Task {
    let result = await (`do`(), stuff())
}

以上代码将生成以下输出:

do() begin
do() end
stuff() begin
stuff() end

相关问题