Ios 在调度屏障中并发执行多个块

Ios 在调度屏障中并发执行多个块,ios,concurrency,queue,grand-central-dispatch,barrier,Ios,Concurrency,Queue,Grand Central Dispatch,Barrier,我想向web服务发送两种类型的请求。第一个是更改后端状态的POST。第二个是从后端检索数据的GET。我希望能够同时发送多个POST请求,因为它们不会导致去同步。但是,我希望GET请求与POST请求一起串行发送(当发送GET请求时,在未收到GET响应的情况下,不能发送POST请求。我使用GET请求的调度屏障实现了这一点。 我的问题是,当执行一个GET请求时,我希望同时发送更多GET请求的选项,并且当接收到最后一个发送的GET请求的响应时,屏障将被打破。 我一直在尝试使用调度屏障来实现这一点,但

我想向web服务发送两种类型的请求。第一个是更改后端状态的POST。第二个是从后端检索数据的GET。我希望能够同时发送多个POST请求,因为它们不会导致去同步。但是,我希望GET请求与POST请求一起串行发送(当发送GET请求时,在未收到GET响应的情况下,不能发送POST请求。我使用GET请求的调度屏障实现了这一点。

我的问题是,当执行一个GET请求时,我希望同时发送更多GET请求的选项,并且当接收到最后一个发送的GET请求的响应时,屏障将被打破。


我一直在尝试使用调度屏障来实现这一点,但到目前为止还没有找到解决方案。也许应该在其他地方寻找解决方案。

如果使用GCD屏障方法,有两件事需要注意:

  • 在网络请求完成之前,分派的任务不应完成(否则,您将同步请求的发出,而不是通过响应过程同步整个请求)

  • 您需要有三种类型的任务可以调度:GET(无障碍)、POST(无障碍)和一些在从GET切换到POST时使用的“切换”任务(有障碍)。此“切换”任务不需要做任何事情,只是为了让您有障碍

    因此,请跟踪上一个请求是GET还是POST,例如,
    lastRequestType
    ,如果新任务的类型不同,则在分派新的网络请求任务之前,首先分派“切换”屏障任务

    显然,“检查
    lastRequestType
    ,发出“切换”屏障并在必要时更新最后一个请求类型,以及发出新请求”的整个过程需要同步,以使其线程安全

  • 还有其他方法。例如,您可以使用操作队列(可能一个用于最新的GET,一个用于最新的POST),并使用依赖项确保POST等待之前的GET,反之亦然。同样,您也需要此“最后一个请求类型为GET或POST”变量,以了解是否需要添加依赖项(同样,这一切都需要正确同步)。此方法允许您:

    • 允许您在
      NSOperation
      子类中包装异步网络请求,从而避免上述第1点的丑陋之处;以及

    • 允许您控制并发程度,这是我在处理大量网络请求时(特别是当它们可能很慢时)总是喜欢做的事情


    如果你原谅我这么说的话,但尽管上述两种方法都有效,但整个想法感觉设计过度。我建议你真的努力,挑战GET和POST不能同时发生的前提。这个决定感觉有点武断,就像一些工程师在随地吐痰,寻找一个简单的解决方案,并建议获取和发布不应该同时发生。在您实现上述内容之前,我会花一点时间弄清楚替代方案是什么样子。我们很多人都使用并发获取和发布来编写应用程序,但没有这种复杂性

    例如,如果我有一系列要执行的POST请求,并希望在完成后发出最终GET请求,我可能会建议使用
    DispatchGroup
    ,在该组中使用组发送各个POST,然后您可以在想要执行最终GET时收到通知:

    let group = DispatchGroup()
    
    networkQueue.async(group: group) {
        // POST 1
    }
    
    networkQueue.async(group: group) {
        // POST 2
    }
    
    group.notify(queue: networkQueue) {
        // final GET
    }
    
    或者,如果您使用的是简单的异步方法(没有信号量,从而避免不必要地阻塞线程),那么仍然可以使用调度组:

    let group = DispatchGroup()
    
    group.enter()
    performFirstPost {
        // POST 1 finished
        group.leave()
    }
    
    group.enter()
    performSecondPost {
        // POST 2 finished
        group.leave()
    }
    
    group.notify(queue: networkQueue) {
        // final GET
    }
    

    经过@Rob的深思熟虑和帮助,我非常感谢,结果是最好不要使用GCD,而是使用OperationQueue。


    代码如下:

    import PlaygroundSupport
    
    import Foundation
    
    final class OperationsManager {
    
        private let serialQueue = DispatchQueue(label: "serialQueue")
    
        private let nonexclusiveOperationsQueue = OperationQueue()
        private let exclusiveOperationQueue = OperationQueue()
    
        func add(operation: Operation, exclusive: Bool) {
            serialQueue.async {
                if exclusive {
                    self.exclusiveOperationQueue.cancelAllOperations()
                    print("Ignore the finish of the previous exclusive operation")
    
                    self.nonexclusiveOperationsQueue.operations.forEach {
                        nonexclusiveOperation in
                        operation.addDependency(nonexclusiveOperation)
                    }
                    self.exclusiveOperationQueue.addOperation(operation)
                } else {
                    self.exclusiveOperationQueue.operations.forEach {
                        exclusiveOperation in
                        operation.addDependency(exclusiveOperation)
                    }
                    self.nonexclusiveOperationsQueue.addOperation(operation)
                }
            }
        }
    
    }
    
    final class BlockedAsynchronousOperation: Operation {
    
        private var semaphore: DispatchSemaphore?
        private let block: (@escaping () -> ()) -> ()
    
        init(block: @escaping (@escaping () -> ()) -> ()) {
            self.block = block
        }
    
        override func cancel() {
            super.cancel()
            semaphore?.signal()
        }
    
        override func main() {
            super.main()
    
            semaphore = DispatchSemaphore(value: 0)
            block {
                [weak self] in
                self?.semaphore?.signal()
            }
            semaphore!.wait()
        }
    
    }
    
    ///////////////////////////////////////////////////////////////////
    func longRunningOperation(
        seconds: Int, completionHandler: @escaping () -> ()) {
        DispatchQueue.global().asyncAfter(
            deadline: .now() + .seconds(seconds),
            execute: completionHandler)
    }
    
    func blockedAsynchronousOperation(
        withID id: String, seconds: Int) -> BlockedAsynchronousOperation {
        return BlockedAsynchronousOperation {
            unblockHandler in
            print("Operation with ID: \(id) started")
            longRunningOperation(seconds: seconds) {
                unblockHandler()
                print("Operation with ID: \(id) finished")
            }
        }
    }
    
    func addOperation(
        withID id: Int,
        exclusive: Bool,
        atSeconds startSeconds: Int,
        duration: Int,
        inOperationsManager operationsManager: OperationsManager) {
        let block = {
            operationsManager.add(operation:
                blockedAsynchronousOperation(
                    withID: (exclusive ? "GET " : "POST ") +
                        String(id), seconds: duration), exclusive: exclusive)
        }
        if startSeconds > 0 {
            DispatchQueue.global().asyncAfter(
                deadline: .now() + .seconds(startSeconds), execute: block)
        } else {
            block()
        }
    }
    
    ///////////////////////////////////////////////////////////////////
    print("global start\n")
    
    let operationsManager = OperationsManager()
    
    addOperation(
        withID: 1,
        exclusive: false,
        atSeconds: 0,
        duration: 7,
        inOperationsManager: operationsManager)
    addOperation(
        withID: 2,
        exclusive: false,
        atSeconds: 0,
        duration: 5,
        inOperationsManager: operationsManager)
    addOperation(
        withID: 1,
        exclusive: true,
        atSeconds: 0,
        duration: 10,
        inOperationsManager: operationsManager)
    addOperation(
        withID: 2,
        exclusive: true,
        atSeconds: 3,
        duration: 10,
        inOperationsManager: operationsManager)
    addOperation(
        withID: 3,
        exclusive: true,
        atSeconds: 10,
        duration: 10,
        inOperationsManager: operationsManager)
    addOperation(
        withID: 3,
        exclusive: false,
        atSeconds: 15,
        duration: 5,
        inOperationsManager: operationsManager)
    addOperation(
        withID: 4,
        exclusive: false,
        atSeconds: 16,
        duration: 5,
        inOperationsManager: operationsManager)
    addOperation(
        withID: 4,
        exclusive: true,
        atSeconds: 28,
        duration: 10,
        inOperationsManager: operationsManager)
    addOperation(
        withID: 5,
        exclusive: true,
        atSeconds: 31,
        duration: 20,
        inOperationsManager: operationsManager)
    addOperation(
        withID: 6,
        exclusive: true,
        atSeconds: 34,
        duration: 2,
        inOperationsManager: operationsManager)
    
    print("\nglobal end\n")
    
    PlaygroundPage.current.needsIndefiniteExecution = true
    

    感谢您的全面回答,以下是我的解释:1.我为什么需要此同步?情况大致如下:GET请求从后端检索项目列表。POST请求标记该列表中的项目(如将其标记为收藏夹)。如果您发送POST请求,并且在收到响应之前发送get请求,则无法保证后端将如何接收这些请求的顺序以及应用程序接收响应的顺序。最坏的情况是:1.POST请求,2.get请求,3.在后端接收请求,4.POST请求在后端收到est,5.在应用程序中收到响应,6.在应用程序中收到响应。在这种情况下,将发生的情况是:用户将尝试将某个项目标记为收藏夹,随后请求刷新,获取该项目标记为收藏夹的反馈,然后获取刷新列表,其中该项目未标记为收藏夹,而sa我的项目实际上在后端被标记为favorite。2.我使用信号量使请求-响应过程成为一个阻塞过程。3.切换屏障任务的想法非常有趣。不幸的是,它不能处理另一个特殊情况。每个GET请求-响应可能需要不同的时间来执行。我想要的是等待n最新的GET请求,忽略在最新的GET请求之前完成的请求。通过使用任何类型的障碍,我仍然必须等待旧的GET请求(可能需要比新的GET请求更长的时间)在其他POST请求再次发送之前完成。Re是使其同步运行的信号量,这很好。令人惊讶的是,我经常在s.O上看到问题。他们没有这样做,他们想知道为什么它没有等待响应。在更大的问题上,我的问题是,为什么不在s.O的完成处理程序中执行GET请求e职位要求