使用NSURLSession和Queue上传数据

时间:2022-02-23 21:04:19

I am designing a chat application and I have set up the following mechanism for users to upload messages. Basically, I push the messages onto a queue and upload them one after the other. When the queue is empty, I call finishedUploading which runs every second and reruns the task if there is anything in the queue.

我正在设计一个聊天应用程序,我已经为用户设置了以下机制来上传消息。基本上,我将消息推送到队列中并一个接一个地上传它们。当队列为空时,我调用每秒运行的finishedUploading,如果队列中有任何内容,则重新运行任务。

var uploadQueue:[UploadMessage]?
let session = NSURLSession.sharedSession()
let lockQueue = dispatch_queue_create("com.dsdevelop.lockQueue", nil)

// RETURNS AMOUNT OF ITEMS STILL IN QUEUE 

func getRemainingActiveUploads() -> Int {
return (self.uploadQueue != nil) ? self.uploadQueue!.count : 0
}

//REMOVES MESSAGE FROM QUEUE ONCE UPLOADED

func removeMessageFromUploadQueue(messageToBeRemoved : UploadMessage) {
if (uploadQueue != nil) {
    dispatch_sync(lockQueue) {
        self.uploadQueue = self.uploadQueue?.filter({$0.date!.compare(messageToBeRemoved.date!) == NSComparisonResult.OrderedSame})
    }
}
}

var uploadTimer : NSTimer?

// CALLED ONLY WHEN UPLOADQUEUE IS EMPTY, RERUNS THE UPLOAD FUNCTION AFTER 1 SECOND
func finishedUploading() {
uploadTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(uploadAllLinks), userInfo: nil, repeats: false)
if (needToRefetch) {
    needToRefetch = false
    newMessageReceived()
}
}

func uploadAllLinks()
{
uploadTimer?.invalidate()
uploadTimer = nil
// suspending queue so they don't all finish before we can show it
session.delegateQueue.suspended = true
session.delegateQueue.maxConcurrentOperationCount = 1

let myUrl = NSURL(string: "http://****")

// create tasks
if (uploadQueue != nil) {
    if (uploadQueue?.count > 0) {
    for message in uploadQueue!
    {
        let request = NSMutableURLRequest(URL:myUrl!)
        request.HTTPMethod = "POST"
        request.timeoutInterval = 10
        request.HTTPShouldHandleCookies=false

        var postString = "sender=" + message.sender! 
        request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);

        let dltask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) in
            if data != nil
            {
                do {
                    let jsonArray = try NSJSONSerialization.JSONObjectWithData(data_fixed!, options:[])
                    dispatch_async(dispatch_get_main_queue(), {

                        if let errorToken = jsonArray["error"] as! Bool? {
                            if  !errorToken  {
                              self.uploadQueue = self.uploadQueue!.filter({$0.date!.compare(message.date!) != NSComparisonResult.OrderedSame})
                                            let remaining = self.getRemainingActiveUploads()
                                            print("Downloaded.  Remaining: \(remaining)")
                                            if (remaining == 0) {
                                                self.finishedUploading()
                                            }
                            }
                            else {

                                            let remaining = self.getRemainingActiveUploads()
                                            print("Downloaded.  Remaining: \(remaining)")
                                            if (remaining == 0) {
                                                self.finishedUploading()
                                            }
                            }
                        }
                        else {

                                        let remaining = self.getRemainingActiveUploads()
                                        print("Downloaded.  Remaining: \(remaining)")
                                        if (remaining == 0) {
                                            self.finishedUploading()
                                        }
                        }

                    })
                }
                catch {
                    print("Error: \(error)")
                }
            }

        })
        print("Queuing task \(dltask)")
        dltask.resume()
    }
        session.delegateQueue.suspended = false
    }
    else {
        finishedUploading()
    }
    // resuming queue so all tasks run
}

}

Now this works fine in the following two cases :

现在这在以下两种情况下工作正常:

  1. Queue is empty -> finishedUploading gets called and uploadAllLinks is run every second to check for items in uploadQueue
  2. 队列为空 - > finallyUploading被调用并且每秒运行uploadAllLinks以检查uploadQueue中的项目
  3. Queue has one item -> the one item gets posted, remaining == 0 hence finishedUploading is called
  4. 队列有一个项目 - >一个项目被发布,剩余== 0因此调用了finishedUploading

However, whenever the queue has more than one item, the first one gets uploaded, if remaining == 0 fails, and then nothing happens. I don't understand why the for loop is not run for the other items in the queue at this point.

但是,只要队列有多个项目,第一个项目就会上传,如果剩余的== 0失败,则没有任何反应。我不明白为什么for循环不会在此时为队列中的其他项运行。

1 个解决方案

#1


1  

I suspect that the problem is your 10-second timeout interval. That begins ticking as soon as the data task is created and terminates the task if it remains idle (without receiving new data) for more than ten seconds.

我怀疑问题是你的10秒超时间隔。一旦创建了数据任务,它就开始计时,如果任务保持空闲(没有接收到新数据)超过十秒,则终止任务。

If you have multiple tasks and the OS is only allowed to upload one or two of them at a time, then any task that is queued up waiting to start will never complete. I don't think the documentation mentions that.

如果您有多个任务且操作系统一次只允许上传其中一个或两个任务,那么排队等待启动的任何任务将永远不会完成。我不认为文件提到了这一点。

In practice, this design makes NSURLSession's queueing less than ideal, and as a result, most folks seem to write their own queues and handle the concurrency limiting on their own, ensuring that each task is created right before it should start running. I would suggest doing something similar:

在实践中,这种设计使得NSURLSession的排队不太理想,因此,大多数人似乎编写自己的队列并自己处理并发限制,确保每个任务都在它开始运行之前创建。我建议做类似的事情:

  • Create a method that starts the next upload in the queue or calls the "everything complete" method if the queue is empty—basically the body of your loop.
  • 创建一个方法,在队列为空时启动队列中的下一个上载或调用“一切完成”方法 - 基本上是循环体。
  • Instead of the loop itself, call that method to start the first upload.
  • 而不是循环本身,调用该方法来启动第一次上载。
  • In your completion handler (inside that method), call that method semi-recursively to start the next upload.
  • 在您的完成处理程序(在该方法内),半递归地调用该方法以开始下一个上载。

Also, 10 seconds is way too short for the timeout interval unless your device is mounted to a wall and is on Wi-Fi with a guaranteed solid signal. Flaky Wi-Fi and weak cellular signals can result in serious latency, so IIRC, the default is 120 seconds, though I've read 60 in various places. Either way, you do not want to use 10 seconds. Such a short timeout would pretty much guarantee that your app will be hopelessly unreliable.

此外,10秒对于超时间隔而言太短,除非您的设备安装在墙上并且在Wi-Fi上具有保证的固定信号。片状Wi-Fi和弱蜂窝信号可能导致严重的延迟,因此IIRC,默认为120秒,尽管我在各个地方读过60。无论哪种方式,你都不想使用10秒。如此短暂的超时几乎可以保证您的应用程序绝对不可靠。

#1


1  

I suspect that the problem is your 10-second timeout interval. That begins ticking as soon as the data task is created and terminates the task if it remains idle (without receiving new data) for more than ten seconds.

我怀疑问题是你的10秒超时间隔。一旦创建了数据任务,它就开始计时,如果任务保持空闲(没有接收到新数据)超过十秒,则终止任务。

If you have multiple tasks and the OS is only allowed to upload one or two of them at a time, then any task that is queued up waiting to start will never complete. I don't think the documentation mentions that.

如果您有多个任务且操作系统一次只允许上传其中一个或两个任务,那么排队等待启动的任何任务将永远不会完成。我不认为文件提到了这一点。

In practice, this design makes NSURLSession's queueing less than ideal, and as a result, most folks seem to write their own queues and handle the concurrency limiting on their own, ensuring that each task is created right before it should start running. I would suggest doing something similar:

在实践中,这种设计使得NSURLSession的排队不太理想,因此,大多数人似乎编写自己的队列并自己处理并发限制,确保每个任务都在它开始运行之前创建。我建议做类似的事情:

  • Create a method that starts the next upload in the queue or calls the "everything complete" method if the queue is empty—basically the body of your loop.
  • 创建一个方法,在队列为空时启动队列中的下一个上载或调用“一切完成”方法 - 基本上是循环体。
  • Instead of the loop itself, call that method to start the first upload.
  • 而不是循环本身,调用该方法来启动第一次上载。
  • In your completion handler (inside that method), call that method semi-recursively to start the next upload.
  • 在您的完成处理程序(在该方法内),半递归地调用该方法以开始下一个上载。

Also, 10 seconds is way too short for the timeout interval unless your device is mounted to a wall and is on Wi-Fi with a guaranteed solid signal. Flaky Wi-Fi and weak cellular signals can result in serious latency, so IIRC, the default is 120 seconds, though I've read 60 in various places. Either way, you do not want to use 10 seconds. Such a short timeout would pretty much guarantee that your app will be hopelessly unreliable.

此外,10秒对于超时间隔而言太短,除非您的设备安装在墙上并且在Wi-Fi上具有保证的固定信号。片状Wi-Fi和弱蜂窝信号可能导致严重的延迟,因此IIRC,默认为120秒,尽管我在各个地方读过60。无论哪种方式,你都不想使用10秒。如此短暂的超时几乎可以保证您的应用程序绝对不可靠。