【问题标题】:NSURLSessionTask never calls back after timeout when using background configuration使用后台配置时,NSURLSessionTask 在超时后从不回调
【发布时间】:2014-06-10 21:53:06
【问题描述】:

我正在使用 NSURLSessionDownloadTask 和后台会话来实现我的所有 REST 请求。这样我就可以使用相同的代码,而不必考虑我的应用程序是在后台还是前台。

我的后端已经死了一段时间,我借此机会测试了NSURLSession 在超时时的行为。

令我大吃一惊的是,我的 NSURLSessionTaskDelegate 回调没有一个被调用过。无论我在NSURLRequestNSURLSessionConfiguration 上设置什么超时,我都没有收到来自iOS 的任何回调,告诉我请求确实以超时结束。

也就是说,当我在后台会话中启动 NSURLSessionDownloadTask 时。应用程序在后台或前台会发生相同的行为。

示例代码:

- (void)launchDownloadTaskOnBackgroundSession {
    NSString *sessionIdentifier = @"com.mydomain.myapp.mySessionIdentifier";
    NSURLSessionConfiguration *backgroundSessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier];
    backgroundSessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
    backgroundSessionConfiguration.timeoutIntervalForRequest = 40;
    backgroundSessionConfiguration.timeoutIntervalForResource = 65;
    NSURLSession *backgroundSession = [NSURLSession sessionWithConfiguration:backgroundSessionConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.timeout.com/"]];
    request.timeoutInterval = 30;
    NSURLSessionDownloadTask *task = [backgroundSession downloadTaskWithRequest:request];
    [task resume];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"URLSession:task:didCompleteWithError: id=%d, error=%@", task.taskIdentifier, error);
}

但是,当我使用默认会话时,我会在 30 秒后收到错误回调(我在请求级别设置的超时)。

示例代码:

- (void)launchDownloadTaskOnDefaultSession {
    NSURLSessionConfiguration *defaultSessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    defaultSessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
    defaultSessionConfiguration.timeoutIntervalForRequest = 40;
    defaultSessionConfiguration.timeoutIntervalForResource = 65;
    NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultSessionConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.timeout.com/"]];
    request.timeoutInterval = 30;
    NSURLSessionDownloadTask *task = [defaultSession downloadTaskWithRequest:request];
    [task resume];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"URLSession:task:didCompleteWithError: id=%d, error=%@", task.taskIdentifier, error);
}

我似乎在文档中找不到任何暗示使用后台会话时超时应该表现不同的东西。

有没有人也遇到过这个问题? 这是错误还是功能?

我正在考虑创建错误报告,但我通常会比错误报告者(六个月)更快地获得关于 SO(几分钟)的反馈。

问候,

【问题讨论】:

  • 你有没有找到解决这个问题的方法?
  • 是和不是。否:我向 Apple 报告了这个错误,Apple 很友好地回答我的错误报告不够完整。我懒得提供超时的测试。很难做到。
  • 是的:我的问题是使用后台会话时 GUI 将等待回调。在这种情况下,如果它不回叫,那会很烦人,因为您无法更新您的 UI。所以我所做的就是在处理从 UI 发起的网络调用并需要反馈时不使用后台会话。
  • 长话短说:我使用后台会话仅在后台执行同步,在处理具有 GUI 反馈的网络调用时使用良好的旧 AFNetworking。我会回答你的问题吗?
  • 我提交了一个错误,因为使用httpbin.org/delay/10 并将请求 timeoutInterval 设置为 5 很容易重现。使用 NSURLConnection 的请求在 5 秒后按预期超时,但 NSURLSession 完成请求需要 10 秒。

标签: ios ios7.1 nsurlsession nsurlsessiondownloadtask nsurlsessionconfiguration


【解决方案1】:

从 iOS8 开始,后台模式下的 NSUrlSession 在服务端没有响应的情况下不会调用这个委托方法。 -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
下载/上传无限期地保持空闲。 当服务器没有响应时,在 iOS7 上调用此委托时出现错误。

一般来说,一个 NSURLSession 后台会话不会使任务失败,如果 电线出了问题。相反,它继续寻找 那时运行请求并重试的好时机。这继续 直到资源超时到期(即 NSURLSessionConfiguration 中的 timeoutIntervalForResource 属性 用于创建会话的对象)。当前的默认值 值是一周!

Quoted information taken from this Source

换句话说,iOS7 中超时失败的行为是不正确的。在后台会话的上下文中,不因为网络问题而立即失败更有趣。所以从 iOS8 开始,NSURLSession 任务即使遇到超时和网络丢失也会继续。但它会一直持续到达到 timeoutIntervalForResource 为止。

所以基本上 timeoutIntervalForRequest 不会在后台会话中工作,但 timeoutIntervalForResource 会。

【讨论】:

  • 这非常精确和有趣。这是某种内幕消息吗? ;)(不仅是因为我很好奇,而是因为我想验证答案)
  • 我从developer forum 的一位 Apple 员工那里得到了这个答案。另外,我已经通过实施验证了这一点。
  • 太好了,谢谢@utsav-dusad。欢迎使用 Stackoverflow :) 提及您的信息来自哪里总是很棒。在这种情况下,我们只能对 BackgroundSession 文档不完整感到遗憾,因为在我看来,这些信息属于那里。
  • 我的实验证实设置 timeoutIntervalForResource 可以完成它的工作。超时后正确调用了 didCompleteWithError。 @utsav-dusad,你摇滚!如果仍然没有人调用它,请确保您了解此处讨论的 Xcode 错误:stackoverflow.com/questions/39495773/…
  • 如果您添加您从以下来源复制的答案的来源会很好:forums.developer.apple.com/thread/22690
【解决方案2】:

DownloadTask 的超时是由 NSURLSessionTaskDelegate 而不是 NSURLSessionDownloadDelegate 引发的

在下载任务期间触发超时(-1001):

等待下载开始。 将触发百分比数据块下载:

URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:

然后在 XCode 调试器中暂停整个应用程序。

等待 30 秒。

使用 XCode 调试器按钮取消暂停应用

来自服务器的http连接应该超时并触发:

-1001 "请求超时。"

#pragma mark -
#pragma mark NSURLSessionTaskDelegate - timeouts caught here not in DownloadTask delegates
#pragma mark -
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{

    if(error){

        ErrorLog(@"ERROR: [%s] error:%@", __PRETTY_FUNCTION__,error);

        //-----------------------------------------------------------------------------------
        //-1001 "The request timed out." 
//        ERROR: [-[SNWebServicesManager URLSession:task:didCompleteWithError:]] error:Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSUnderlyingError=0x1247c42e0 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, NSErrorFailingURLStringKey=https://directory.clarksons.com/api/1/dataexport/ios/?lastUpdatedDate=01012014000000, NSErrorFailingURLKey=https://directory.clarksons.com/api/1/dataexport/ios/?lastUpdatedDate=01012014000000, _kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102, NSLocalizedDescription=The request timed out.}
        //-----------------------------------------------------------------------------------


    }else{
        NSLog(@"%s SESSION ENDED NO ERROR - other delegate methods should also be called so they will reset flags etc", __PRETTY_FUNCTION__);
    }
}

【讨论】:

  • 好的,这是连接建立后两个数据块之间的超时时间。另一种更频繁的超时呢:当请求的 URL 甚至没有响应时?在这种情况下,超时是您在放弃之前等待来自 HTTP 服务器的(标头)响应的时间。在许多语言/库中,这些超时的处理方式不同。
  • 我必须承认我已经有一段时间没有尝试过了,所以 Apple 现在可能已经解决了这个问题。
  • 这里是一个讨论不同类型超时的 Java 相关问题的链接:stackoverflow.com/questions/18184899/…
  • 目前处理的两个即时消息是:-1001(请求超时)。 -1005 "网络连接丢失。[我认为是由测试人员从我的 mac 离开 wifi 范围触发的]
【解决方案3】:

UIApplicationDelegate 中有一个方法可以让你了解后台进程。

-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler

如果有多个会话,您可以通过

来识别您的会话
if ([identifier isEqualToString:@"com.mydomain.myapp.mySessionIdentifier"]) 

还有一种方法用于定期通知进度。在这里你可以检查 NSURLSession 的状态

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite


NSURLSessionTaskStateRunning = 0,                   
NSURLSessionTaskStateSuspended = 1,
NSURLSessionTaskStateCanceling = 2,                   
NSURLSessionTaskStateCompleted = 3,              

【讨论】:

  • 谢谢,但答案与 user2260054 相同。我确实实现了这个回调[并且它没有被调用],这不是问题。您指出的回调仅在您的应用程序将在后台启动时使用,以告诉您使用提供的标识符创建会话。并在您处理完会话任务后调用提供的回调。我指出的问题发生在应用程序在后台和前台使用所谓的“后台”NSURLSession 时。当您的应用处于前台时,您指出的回调永远不会被调用。
  • 在NSURLSession委托方法中,可以查看NSURLSession的状态
  • 是的。但整个事情是尝试从不响应(超时)的服务器下载。因此,不应调用 didWriteData,因为服务器永远不会返回任何数据。我想知道为什么在具有后台配置的 NSURLSession 上运行时请求从不回调,而在默认配置上运行时会回调。所有请求总有一天会以某种方式回调。
  • 是的。它不会因为超时而被调用。所以进度也会停止并且 -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error 方法将被调用。从这里你可以知道错误的原因。是超时问题还是任何网络问题。
【解决方案4】:

和你一样,我正在开发的应用程序总是使用后台会话。我注意到的一件事是,如果它中断工作连接,则超时工作正常,即传输成功开始。但是,如果我为不存在的 URL 启动下载任务,它不会超时。

鉴于您说您的后端已经死了一段时间,这听起来很像您所看到的。

很容易复制。只需将超时设置为 5 秒。使用有效的 URL,您将获得一些进度更新,然后看到它超时。即使有后台会话。使用无效的 URL,只要您调用 resume,它就会安静下来。

【讨论】:

  • 感谢您的意见。这就是为什么在大多数框架上你经常有两个甚至三个不同的超时类型。至少连接建立超时(不工作的那个)和数据传输超时(你正在谈论的那个工作)。有时您甚至会在“请求已发送”和“第一个字节已收到”之间出现第三次超时。
【解决方案5】:

我遇到了完全相同的问题。我发现的一种解决方案是使用两个会话,一个用于使用默认配置的前台下载,另一个用于使用后台配置的后台下载。当更改为背景/前景时,生成恢复数据并将其从一个传递到另一个。但我想知道您是否找到了其他解决方案。

【讨论】:

  • 我们之前在 HttpClient 中使用了旧的方式,所以我们一直在前台使用它,并在后台使用后台会话。 Takes 创建了一些非常混乱的代码和双重测试。您的解决方案对我来说似乎更具吸引力。
  • 请记住,尽管它们可能仍然存在在应用程序处于活动状态时发起的请求,但无论如何您都希望在后台执行(因为如果用户发送应用程序,它们不会停止执行在背景中)。对于 UI 不依赖的所有请求都是如此。
  • 我有一段时间没有测试这个,很遗憾听到问题仍然没有解决。
猜你喜欢
  • 2017-07-10
  • 1970-01-01
  • 2016-10-13
  • 2017-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-11
相关资源
最近更新 更多