3

我有一組異步執行的請求。但是,每個下一個請求只應在前一個請求完成時纔開始(由於數據依賴性)。如何執行一個接一個的多個異步請求

由於所有請求都應該按照正確的順序完成,因此DispatchGroup()似乎沒用。

我目前實施DispatchSemaphore(),但我覺得這不是最好的解決方案,因爲我想確保所有請求都在後臺執行。

let semaphore = DispatchSemaphore(value: requests.count) 

for request in requests { 
    apiManager().performAsyncRequest(request, failure: { error in 
     print(error); semaphore.signal() 
     }) { print(「request finished successful」) 
     // Next request should be performed now 
     semaphore.signal() 
    } 
} 
semaphore.wait() 

有沒有更好的方法來執行此操作?

注意:基於執行下面的答案之一我遇到apiManager()不是線程安全的(由於使用Realm數據庫)。

爲了保持這個問題,明確並與performAsyncRequest一個線程安全的定義,認爲在一個線程安全的方式回答:

public func performAsyncRequest(_ requestNumber: Int, success: @escaping (Int) -> Void)->Void { 
    DispatchQueue(label: "performRequest").async { 
     usleep(useconds_t(1000-requestNumber*200)) 
     print("Request #\(requestNumber) starts") 
     success(requestNumber) 
    } 
} 

解決方案與DispatchSemaphore

 let semaphore = DispatchSemaphore(value: 1) 
     DispatchQueue(label: "requests").async { 
      for requestNumber in 0..<4 { 
       semaphore.wait() 
       performAsyncRequest(requestNumber) { requestNumber in 
         print("Request #\(requestNumber) finished") 
         semaphore.signal() 
       } 
      } 
     } 

隨着預期的輸出:

Request #0 starts 
Request #0 finished 
Request #1 starts 
Request #1 finished 
Request #2 starts 
Request #2 finished 
Request #3 starts 
Request #3 finished 

不成功的嘗試Operation

var operations = [Operation]() 

    for requestNumber in 0..<4 { 
     let operation = BlockOperation(block: { 
      performAsyncRequest(requestNumber) { requestNumber in 
       DispatchQueue.main.sync { 
        print("Request #\(requestNumber) finished") 
       } 
      } 
     }) 

     if operations.count > 0 { 
      operation.addDependency(operations.last!) 
     } 
     operations.append(operation) 
    } 

    let operationQueue = OperationQueue.main 
    operationQueue.addOperations(operations, waitUntilFinished: false) 

用不正確的輸出:

Request #0 starts 
Request #1 starts 
Request #2 starts 
Request #3 starts 
Request #0 finished 
Request #3 finished 
Request #2 finished 
Request #1 finished 

我的感覺是,它也應該能夠得到這個與Operation的工作,但我不知道它是否會比使用DispatchSemaphore更好。

+0

您定位的是哪個Swift版本? – xpereta

+0

Swift 3.我剛剛用DispatchSemaphore的實現編輯了這個問題,但我想知道在這種情況下是否應該使用它。 – Taco

+0

你是指「所有請求都在後臺執行」是什麼意思?如果apiManager()不是線程安全的,則不可能執行任何調用來在後臺線程上執行AsyncRequest。 – xpereta

回答

1

你在正確的軌道與DispatchSemaphore上,以確保前一個已完成之前asyncronous呼叫未啓動。我只想確保所管理的呼叫到asyncronous API代碼在後臺運行:

let backgroundQueue = DispatchQueue(label: "requests") 
let semaphore = DispatchSemaphore(value: 1) 

backgroundQueue.async { 
    var requestNumber = 1 

    for request in requests { 
     semaphore.wait() 

     let currentRequestNumber = requestNumber 

     print("Request launched #\(requestNumber)") 

     apiManager().performAsyncRequest(request, 
     failure: { 
      error in 
      print("Request error #\(currentRequestNumber)") 
      semaphore.signal() 
     }) { 
      print("Request result #\(currentRequestNumber)") 
      semaphore.signal() 
     } 

     requestNumber = requestNumber + 1 
    } 
} 

代碼將immediatelly繼續執行,同時在for循環在後臺循環運行,並等待後開始每個請求前一個完成。

或者,如果apiManager()不是線程安全的:

let semaphore = DispatchSemaphore(value: 1) 

var requestNumber = 1 

for request in requests { 
    semaphore.wait() 

    let currentRequestNumber = requestNumber 

    print("Request launched #\(requestNumber)") 

    apiManager().performAsyncRequest(request, 
    failure: { 
     error in 
     print("Request error #\(currentRequestNumber)") 
     semaphore.signal() 
    }) { 
     print("Request result #\(currentRequestNumber)") 
     semaphore.signal() 
    } 

    requestNumber = requestNumber + 1 
} 

這有限制,即for循環將在執行完最後一個請求開始執行。但是如果你所調用的代碼不是線程安全的,那麼這是沒有辦法的。

+0

不幸的是,我沒有得到這個工作。由於底層Realm數據庫發生以下錯誤:'終止於類型realm的未捕獲異常:: IncorrectThreadException:從不正確線程訪問的區域'。但是,如果不使用背景威脅,則實現會導致死鎖。 – Taco

+0

目前,我也正在使用NSOperation的實現,如其他答案中的建議。無論如何,這個用例是不是更適合'NSOperation'? – Taco

+0

請更改問題以闡明apiManager不是線程安全的,請參閱:http://stackoverflow.com/a/25670511/563802。 – xpereta

1

您可以使用NSOperationQueue,並添加每個請求的操作爲

let firstOperation: NSOperation 
let secondOperation: NSOperation 
secondOperation.addDependency(firstOperation) 

let operationQueue = NSOperationQueue.mainQueue() 
operationQueue.addOperations([firstOperation, secondOperation], waitUntilFinished: false) 
+0

此答案如何解決在上次異步調用完成之前不會啓動一個cal的要求? – xpereta

+0

@xpereta NSOperationQueue支持依賴關係,在這裏我已經使第二個操作依賴於第一個操作,所以直到第一個操作完成纔會啓動https://developer.apple.com/reference/foundation/operation – Misha

+0

在問題中對performAsyncRequest的調用是異步的並會立即完成。下一個請求無法啓動,直到前一個完成關閉被執行。 – xpereta