在 OperationQueue 上运行 DataTask 时忽略 NSURLSession .waitsForConnectivity 标志

Posted

技术标签:

【中文标题】在 OperationQueue 上运行 DataTask 时忽略 NSURLSession .waitsForConnectivity 标志【英文标题】:NSURLSession .waitsForConnectivity flag ignored when DataTask being run on OperationQueue 【发布时间】:2021-12-07 04:07:22 【问题描述】:

我有一个手写类 MyURLRequest,它实现了 Operation。在它里面创建 URLSession,配置它

public init(shouldWaitForConnectivity: Bool, timeoutForResource: Double?) 
    baseUrl = URL(string: Self.relevantServerUrl + "api/")
    self.shouldWaitForConnectivity = shouldWaitForConnectivity
    self.timeoutForResource = timeoutForResource
    super.init()
    localURLSession = URLSession(configuration: localConfig, delegate: self, delegateQueue: nil)

public var localConfig: URLSessionConfiguration 
    let res = URLSessionConfiguration.default
    res.allowsCellularAccess = true
    if let shouldWaitForConnectivity = shouldWaitForConnectivity 
        res.waitsForConnectivity = shouldWaitForConnectivity
        if let timeoutForResource = timeoutForResource 
            res.timeoutIntervalForResource = timeoutForResource
        
    
    return res

创建 URLRequest、dataTask,然后在 OperationQueue 上运行。操作的方法是这样的

override open func start() 
    if isCancelled 
        isFinished = true
        return
    
    
    startDate = Date()
    sessionTask?.resume()
    localURLSession.finishTasksAndInvalidate()


override open func cancel() 
    super.cancel()
    sessionTask?.cancel()

MyURLRequest 还实现了 URLSessionDataDelegateURLSessionTaskDelegate 以及它自己的 URLSession 的代理。

waitsForConnectivity NSURLSessionConfiguration 的标志有问题。在构造函数中,我将其设置为 true,但此标志被忽略。在运行时,当网络关闭时,请求立即结束,错误 -1009。 URLSessionTaskDelegate 的方法 urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) 立即触发。 func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) 根本没有被调用。

原因绝对不是,waitsForConnectivity 标志设置不正确:我检查了 urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) 收到的任务中的配置 , 并且 waitsForConnectivity == true。

我也尝试在没有操作队列的情况下发出请求,结果很好 - 表现如预期。也许与 OperationQueue 有关。非常感谢您的帮助!

更新: 似乎问题的根源是操作发布得太早(当请求尚未完成时)。我尝试使用 DispatchGroup() 来同步它们:

override open func start() 
    if isCancelled 
        isFinished = true
        return
    
    startDate = Date()

    dispatchGroup.enter()
    sessionTask?.resume()
    dispatchGroup.wait()

    localURLSession.finishTasksAndInvalidate()

其中 .leave() 在 URLSessionDelegate 的方法中被调用。没有任何改变,仍然没有等待连接。

更新: 这是我在 didCompleteWithError 中遇到的错误:

Error Domain=NSURLErrorDomain Code=-1009 "" UserInfo=_kCFStreamErrorCodeKey=50, NSUnderlyingError=0x7fc319112de0 Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo=_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <6388AD46-8497-40DF-8768-44FEBB84A8EC>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <6388AD46-8497-40DF-8768-44FEBB84A8EC>.<1>",
    "LocalDataTask <26BCBD73-FC8B-4A48-8EA2-1172ABB8093C>.<1>"
), NSLocalizedDescription=., NSErrorFailingURLStringKey=

【问题讨论】:

【参考方案1】:

我相信问题的根源在于您使用了finishTasksAndInvalidate。看起来您依赖该方法同步等待所有挂起的任务完成,但根据文档,它不是这样工作的。

以下是对我认为正在发生的事情的更深入的解释。默认情况下,只要start 方法返回,Operation 就被认为是完整的。这绝对不是您需要的,因为任务必须异步完成。 Operation能够开箱即用地支持这种行为。

您的start 立即返回,早在会话有任何时间完成您开始的任务之前。然后,随着操作完成,队列将其移除。这通常最终成为该实例的唯一所有者。如果这是真的,它会启动操作 deinit 进程,最终释放URLSession。在那个点,会话看起来可能会进行一些清理并终止任何未完成的任务,将一些调用转发给它的委托。我不确定 URLSession 是否这样做,但根据您所看到的,听起来可能。

要实现您想要的,我认为您需要将 NSOperation 子类重新构建为完全异步的,并且仅在启动的任务完成时完成。

构建一个完全线程安全的异步NSOperation 子类真的很痛苦。如果这不是您以前解决过的问题,您可以在此处查看实现:https://github.com/ChimeHQ/OperationPlus

【讨论】:

我也认为是这种情况,但评论那行没有做任何事情:( 是的,这完全符合我的预期。问题是您的 NSOperation 生命周期与 sessionTask 无关。因此,操作提前终止,释放会话并提前结束所有任务。 (假设 localURLSession 归操作所有) 但是如果操作解除分配然后会话被解除分配,委托方法是如何触发的呢?操作对象充当会话的委托 好问题!为什么不在您的委托方法中设置断点并找出答案? 我试过了!委托方法“withError”在 deinit 操作之前工作

以上是关于在 OperationQueue 上运行 DataTask 时忽略 NSURLSession .waitsForConnectivity 标志的主要内容,如果未能解决你的问题,请参考以下文章

Swift - 使用两个不同的OperationQueue和KVO时应用程序崩溃

Swift - 使用两个不同的 OperationQueue 和 KVO 时应用程序崩溃

Swift 5.5 并发:如何序列化异步任务以用 maxConcurrentOperationCount = 1 替换 OperationQueue?

应用程序生命周期中的 Alamofire 请求阻塞

无法快速获得字符串响应

RestKit 网络限制在并行请求运行时阻止其他调用