如何制作异步 Swift 函数“@synchronized”?
How to make an async Swift function "@synchronized"?
我想创建一个异步函数,它本身使用异步调用。我还想确保在任何时刻都只有一个呼叫被主动处理。所以我想要一个 async
@synchronized
函数。
怎么做?将函数的主体包装在 dispatchQueue.sync {}
中不起作用,因为它需要同步代码。此外,DispatchQueue
似乎一般不设计为具有要执行的异步代码块/任务。
这段代码与硬件通信,本质上是异步的,这就是为什么我想要我的库有一个异步接口。 (我不想在通信阶段发生时阻止应用程序。)但是某些操作不能在硬件上并行执行,所以我必须进行同步,这样某些操作就不会同时发生.
您可以让每个 Task
等待前一个。你可以使用 actor 确保你一次只有 运行 一个。诀窍是,由于 actor 可重入性,您必须将“等待优先 Task
”逻辑放在同步方法中。
例如,您可以这样做:
actor Experiment {
private var previousTask: Task<Void, Error>?
func startSomethingAsynchronous() {
previousTask = Task { [previousTask] in
let _ = await previousTask?.result
try await self.doSomethingAsynchronous()
}
}
private func doSomethingAsynchronous() async throws {
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "Task", signpostID: id, "Start")
try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)
os_signpost(.end, log: log, name: "Task", signpostID: id, "End")
}
}
现在我正在使用 os_signpost
,所以我可以从 Xcode Instruments 观察这个序列行为。无论如何,您可以像这样开始三个任务:
import os.log
private let log = OSLog(subsystem: "Experiment", category: .pointsOfInterest)
class ViewController: NSViewController {
let experiment = Experiment()
func startExperiment() {
for _ in 0 ..< 3 {
Task { await experiment.startSomethingAsynchronous() }
}
os_signpost(.event, log: log, name: "Done starting tasks")
}
...
}
并且 Instruments 可以直观地演示顺序行为(其中 ⓢ
向我们展示了所有任务的提交完成的位置),但是您可以在时间轴上看到任务的顺序执行:
其实我喜欢把这个串行行为抽象成自己的类型:
actor SerialTasks<Success> {
private var previousTask: Task<Success, Error>?
func add(block: @Sendable @escaping () async throws -> Success) {
previousTask = Task { [previousTask] in
let _ = await previousTask?.result
return try await block()
}
}
}
然后您需要此串行行为的异步函数将使用上面的代码,例如:
class Experiment {
let serialTasks = SerialTasks<Void>()
func startSomethingAsynchronous() async {
await serialTasks.add {
try await self.doSomethingAsynchronous()
}
}
private func doSomethingAsynchronous() async throws {
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "Task", signpostID: id, "Start")
try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)
os_signpost(.end, log: log, name: "Task", signpostID: id, "End")
}
}
我想创建一个异步函数,它本身使用异步调用。我还想确保在任何时刻都只有一个呼叫被主动处理。所以我想要一个 async
@synchronized
函数。
怎么做?将函数的主体包装在 dispatchQueue.sync {}
中不起作用,因为它需要同步代码。此外,DispatchQueue
似乎一般不设计为具有要执行的异步代码块/任务。
这段代码与硬件通信,本质上是异步的,这就是为什么我想要我的库有一个异步接口。 (我不想在通信阶段发生时阻止应用程序。)但是某些操作不能在硬件上并行执行,所以我必须进行同步,这样某些操作就不会同时发生.
您可以让每个 Task
等待前一个。你可以使用 actor 确保你一次只有 运行 一个。诀窍是,由于 actor 可重入性,您必须将“等待优先 Task
”逻辑放在同步方法中。
例如,您可以这样做:
actor Experiment {
private var previousTask: Task<Void, Error>?
func startSomethingAsynchronous() {
previousTask = Task { [previousTask] in
let _ = await previousTask?.result
try await self.doSomethingAsynchronous()
}
}
private func doSomethingAsynchronous() async throws {
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "Task", signpostID: id, "Start")
try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)
os_signpost(.end, log: log, name: "Task", signpostID: id, "End")
}
}
现在我正在使用 os_signpost
,所以我可以从 Xcode Instruments 观察这个序列行为。无论如何,您可以像这样开始三个任务:
import os.log
private let log = OSLog(subsystem: "Experiment", category: .pointsOfInterest)
class ViewController: NSViewController {
let experiment = Experiment()
func startExperiment() {
for _ in 0 ..< 3 {
Task { await experiment.startSomethingAsynchronous() }
}
os_signpost(.event, log: log, name: "Done starting tasks")
}
...
}
并且 Instruments 可以直观地演示顺序行为(其中 ⓢ
向我们展示了所有任务的提交完成的位置),但是您可以在时间轴上看到任务的顺序执行:
其实我喜欢把这个串行行为抽象成自己的类型:
actor SerialTasks<Success> {
private var previousTask: Task<Success, Error>?
func add(block: @Sendable @escaping () async throws -> Success) {
previousTask = Task { [previousTask] in
let _ = await previousTask?.result
return try await block()
}
}
}
然后您需要此串行行为的异步函数将使用上面的代码,例如:
class Experiment {
let serialTasks = SerialTasks<Void>()
func startSomethingAsynchronous() async {
await serialTasks.add {
try await self.doSomethingAsynchronous()
}
}
private func doSomethingAsynchronous() async throws {
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "Task", signpostID: id, "Start")
try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)
os_signpost(.end, log: log, name: "Task", signpostID: id, "End")
}
}