0

假设一个类是这样安排的:

class A {
    func a() {
        doStuff()
    }
    func b() {
        Task {
            something()
            await someLongRunningThing()
            somethingElse()
        }
    }
    func c() {
        doOtherStuff()
    }
}

如何确保一次只有一个函数处于活动状态,并且每个函数的执行都以 FIFO 方式执行?这将包括Taskin的主体b()。如果Task正在执行,则该类的其他函数都不能进入,它们将排队并在没有交错的情况下操作 FIFO。

由于成员函数可能会在主线程上被调用,因此不应该涉及线程阻塞。

这是我面临的常见情况,我想不出一个合理的解决方案。一开始我很兴奋actor,但是当函数中有一个时,actor允许重新进入await,允许竞争条件。我唯一能找到的就是这里。但是在这里,作者实际上只是将问题进一步推到调用层次结构上。

4

3 回答 3

2

怎么样:A 类有自己的串行队列,您的函数将所有工作提交给它,每个都作为一个块。这可以让您进行 FIFO 处理。如果Task不是内联的,那么 b() 的块可以使用一些同步(进入/离开?)来保持私有队列直到任务完成。

由于所有三个函数都只分派和返回,因此它们的调用者线程不会被阻塞。

于 2022-01-03T18:40:59.033 回答
0

这是我想出的。我不考虑终止流,因为在我的情况下我不需要。

class ActionQueue {
    typealias Action = () async -> Void
    
    func enqueue(action: @escaping Action) {
        DispatchQueue.main.async { [weak self] in
            guard let sself = self else { return }
            if let enqueueAction = sself.enqueueAction {
                enqueueAction(action)
            } else {
                sself.initialQueue.append(action)
            }
        }
    }
    
    private var enqueueAction: ((@escaping Action) -> Void)? = nil
    
    //necessary because the AsyncStream build block doesn't necessary happen
    //on main thread. Otherwise we could lose Actions.
    private var initialQueue = [Action]()
    
    private var stream: AsyncStream<Action> {
        AsyncStream { continuation in
            DispatchQueue.main.sync {
                enqueueAction = { action in
                    continuation.yield(action)
                }
                initialQueue.forEach { action in
                    enqueueAction!(action)
                }
            }
        }
    }
    
    init() {
        Task.init {
            for await action in stream {
                await action()
            }
        }  
    }
}

用法:

class A {
    
    func a() {
        actionQueue.enqueue {
            doOtherStuff()
        }
    }
    
    func b() {
        actionQueue.enqueue {
            something()
            await someLongRunningThing()
            somethingElse()
            await MainActor.run {
                mustBeOnMainThread()
            }
        }
    }
  
    private let actionQueue = ActionQueue()
}
于 2022-01-05T17:09:10.260 回答
0

在这种情况下你不能。在主线程上运行的函数上生成工作(Task {}),在调用另一个函数之前完全执行该函数并且没有阻塞是矛盾的。

如果您将其设置为参与者,则一次只有一个功能处于活动状态,但鉴于您添加了暂停点(等待),参与者可能会重新进入(在已经暂停工作项的参与者上安排更多工作)。这也是避免优先级倒置的功能。

虽然它在新的并发模型下没有编译器支持,但可以使用 NSLock 或 os_unfair_lock 来实现自己的同步。这将为您提供一个 FIFO 类,但您仍然必须在 Main 上安排其结果。

如果您发布一个更现实的示例来说明您要完成的工作,也许有人可以提出更好的解决方案。

于 2022-01-05T16:23:56.043 回答