在调度障碍

时间:2017-11-30 23:57:01

标签: ios concurrency queue grand-central-dispatch barrier

我想向Web服务发送两种类型的请求。第一个是POST,它改变了后端状态。第二个是GET,它从后端检索数据。我希望能够同时发送多个POST请求,因为它们不会导致失步。但是,我希望GET请求与POST请求相关联地发送(当发送GET请求时,在没有收到GET响应的情况下不能发送POST请求。我使用GET请求的调度屏障实现了此请求。

我的问题是,当一个GET请求正在执行时,我希望同时发送更多GET请求的选项以及在收到最后一个发送的GET请求响应时中断的障碍。

我一直在尝试使用调度障碍来完成这项工作但到目前为止还没有找到解决方案。也许应该在其他地方寻找解决方案。

2 个答案:

答案 0 :(得分:0)

如果您使用GCD屏障方法,有两件事要敏感:

  1. 在完成网络请求之前,您的调度任务不应完成(否则您将同步请求的发出,而不是整个请求 - 响应过程)。

  2. 您需要三种类型的可分派任务:GET(没有障碍),POST(没有障碍)和一些“切换”任务(带障碍) )当你从GET切换到POST时,你会使用它。这个“切换”任务不需要做任何事情,但只是在那里,所以你有一个障碍。

    因此,请跟踪上次请求是GET还是POST,例如: lastRequestType,如果新任务的类型不同,则在调度新的网络请求任务之前先调度“switch”屏障任务。

    显然,“检查lastRequestType,发出'切换'屏障并在必要时更新上次请求类型,并发出新请求”的整个过程需要同步才能使其线程安全。

  3. 还有其他方法。例如,您可以使用操作队列(可能一个用于最近的GET,一个用于最近的POST),并使用依赖关系来确保POST等待先前的GET,反之亦然。同样,你需要这个“最后一个请求类型是GET或POST”变量,以便知道你是否需要添加依赖项(而且,这一切都需要正确同步)。这种方法可以让你:

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

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

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

    例如,如果我有一系列我想要执行的POST请求,并希望在完成后发出最终的GET请求,我可能会建议使用DispatchGroup,其中各个帖子将使用组,然后您可以在想要进行最终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
    }
    

答案 1 :(得分:0)

经过很多思考和@Rob的大量帮助后我非常感激,结果是最好不要使用GCD而是使用OperationQueue。

This is a gist with a playground of the implementation along with the tests.
这是它的代码:

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