如何保证异步请求中的正确顺序

Posted

技术标签:

【中文标题】如何保证异步请求中的正确顺序【英文标题】:How to guarantee the correct order in async request 【发布时间】:2017-06-20 07:36:50 【问题描述】:

我有一个使用 Alamofire 发出 URL 请求的 ios 应用程序。当它们之间的时间很短时,我有时会发现请求以错误的顺序到达。据我了解,这与异步请求的性质有关。有没有办法保证请求的正确顺序?我一直在想你可以等待每个请求完成,因为你有一个完成处理程序,或者你可以在服务器端处理这个,每个请求都有一个时间戳,这样服务器就可以丢弃时间戳较低的请求。我不知道什么是最好的解决方案。

到目前为止我的代码:

Alamofire.request(
     defaults.string(forKey: "APIurl")! + path,
     method: httpMethod,
     parameters: parameters,
     encoding: JSONEncoding.default,
     headers: headers).responseJSON
      response in

     // Check if the request was successful
     var success = false
     if (response.result.isSuccess) 
         let statusCode = response.response!.statusCode
         if (statusCode == 200) 
            success = true
         else 
            showAlert("COULD_NOT_COMPLETE_TASK_TITLE", message: "TRY_AGAIN_LATER")
        
    


我使用滑块来更改 0 到 100 之间的值。所以在我的例子中,请求的顺序是至关重要的。假设我将滑块从 50 更改为 60。对于异步请求,它有时会先执行 60,然后执行 50。这是一个问题,因为它是以这种方式发送到我的 API 并在数据库中保存最新值(在本例中为 50)甚至虽然我想要的值是 60。

【问题讨论】:

你不能保证异步请求的顺序,你必须做你的逻辑处理请求返回后做什么。 如果顺序很重要,那么要么按顺序分派请求,要么在所有请求完成后协调响应 保证他们完成顺序的唯一方法是在第一个请求完成之前不发出第二个请求(这是解决问题的一种非常愚蠢的方法)。 Wei Jay 是对的,您应该编写不依赖于响应进入顺序的代码。但是,如果没有更多上下文,我们无法帮助您解决该问题。但是,话又说回来,既然我们已经指出您需要以任何顺序来处理它们,您也可以弄清楚。 问题在于,如果它们以错误的顺序到达,API 将错误地相互渗透。我有一个滑块,您可以从 0 滑到 100。假设我将它从 50 滑到 60,请求以相反的顺序发送(60 到 50)。然后我会将“错误”数据发送到 API。有什么方法可以丢弃执行时间较长的请求? 您可以分配序列号或将请求中发送的客户端时间戳应用到服务器,然后让它进行排序。或者,如果您只关心最新的请求,请在发送后续请求之前取消先前的请求。或者,如果有持续不断的活动,也许您追求基于套接字的连接而不是 RESTful API。有很多方法可以解决这个问题。我建议你给我们一个你试图解决的现实世界的实际问题,而不是抽象地问我们这个问题。 【参考方案1】:

如果线程是串行的,在您的情况下是串行的,那么顺序将始终与您输入的相同。因此,在同一个串行线程上异步调用一些操作将强制这些操作保持该顺序。

您的问题是您没有调用这些操作,Alamofire 正在调用它们。顺序被保留,但它取决于何时接收和解析响应。这意味着您可能无法控制调用异步操作的顺序。

您有 2 种序列化响应的方式:

    在调用下一个请求之前,您需要等待每个响应完成。如果您的响应是标准的(看起来都一样),您只需要一些管理器来存储一系列请求并且在前一个请求完成之前不会调用新的请求。这可能有点慢,因为没有理由(或者至少在您的情况下看起来是这样)不同时执行请求。

    对响应进行序列化,以便按照与输入相同的顺序调用它们。这意味着您随时调用请求,并且将随时调用响应。但是,一旦收到响应,您将检查其他响应是否完整,只有当它们完成时,您才会触发此响应的回调。这又意味着要有一些管理器来序列化那里的响应。

所以对于第二个,你需要这样的东西:

SerializedRequestManager.performRequest(request, myCallbackClosure)

然后管理器会将请求保存到一些请求包装器数组中,例如:

let requestWrapper = RequestWrapper(request, myCallbackClosure, isCompleted: false)
self.requestPool.append(requestWrapper)
AF.performRequest(request, myInternalClosure)

然后在响应 (myInternalClosure) 时,您需要设置正确的包装器以使响应为 true,然后从数组的开头刷新响应。然后必须从数组中删除所有完成的响应:

requestWrapper.responseData = data // Same wrapper that was just added into the array
requestWrapper.responseError = error // Same wrapper that was just added into the array
requestWrapper.isCompleted = true
self.flushResponses()

那么flushResponses:

var newPool = [RequestWrapper]() // This is where all the pending items will stay
var shouldFlushResponse = true // Will be set to false with first request that was not completed
self.requestPool.forEach  wrapper in
    if wrapper.isCompleted && shouldFlushResponse 
       wrapper.callback(wrapper.responseData, wrapper.responseError) // perform response closure
     else 
       shouldFlushResponse = false
       newPool.append(wrapper)
    

self.requestPool = newPool

但是你需要非常小心这里的多线程。数组requestPool上的所有操作都应该在同一个线程上完成,但它可以是你想要的任何线程。

【讨论】:

【参考方案2】:

好吧,如果请求的顺序对您的情况至关重要,那么您应该选择 NSOperationQueue,这是确保您的请求顺序的唯一方法。

关注this教程有一个边框思路

【讨论】:

以上是关于如何保证异步请求中的正确顺序的主要内容,如果未能解决你的问题,请参考以下文章

Vue 数据同步获取

dva 如何把同步改为异步请求

异步 HTTP 请求的顺序

基础概念——何为同步异步

详解Ajax请求——多个异步请求的执行顺序

同步异步,并发并行概念的理解