如何制作异步 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")
    }
}