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