如何在不等待结果的情况下异步调用异步函数
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
。这引入了 first
和 second
之间的竞争,您无法保证它们 运行 的顺序。 (在我的测试中,大部分时间它在 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.
假设我有以下功能。
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
。这引入了 first
和 second
之间的竞争,您无法保证它们 运行 的顺序。 (在我的测试中,大部分时间它在 first
之前 运行s second
,但它仍然偶尔会在 second
之前 运行 first
。 )
所以,问题是,你真的关心这两个任务的启动顺序吗?如果是这样,您可以通过(显然)在调用 second
之后放置 Task { await first() }
来消除竞争。或者您只是想确保 second
不会等待 first
完成?在那种情况下,这已经是行为并且不需要更改您的代码。
您问的是:
What if
await first()
needs to be run on the same queue assecond()
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 theTask.detached(priority:operation:)
class method.