【问题标题】:NSOperation deadlocks and blocks NSOperationQueueNSOperation 死锁和阻塞 NSOperationQueue
【发布时间】:2012-09-11 12:22:04
【问题描述】:

我使用 NSOperation 的子类通过 Amazon 的 iOS 开发工具包 (v1.3.2) 将大文件上传到 AWS S3。这一切都很好,但一些 beta 测试人员遇到了死锁(iOS 5.1.1)。结果是调度操作的 NSOperationQueue 被阻塞,因为一次只允许一个操作运行。问题是我无法重现该问题,而 Beta 测试人员每次都会遇到此问题。

由于 AWS iOS 开发工具包的工作方式,该操作非常复杂。但是,据我所知,根据我的测试,该问题与 AWS iOS 开发工具包无关。该操作的主要方法粘贴在下面。操作main方法的思路是基于this Stack Overflow question

- (void)main {
    // Operation Should Terminate
    _operationShouldTerminate = NO;

    // Notify Delegate
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.delegate operation:self isPreparingUploadWithUuid:self.uuid];
    });

    // Increment Network Activity Count
    [self incrementNetworkActivityCount];

    // Verify S3 Credentials
    [self verifyS3Credentials];

    while (!_operationShouldTerminate) {
        if ([self isCancelled]) {
            _operationShouldTerminate = YES;

        } else {
            // Create Run Loop
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
    }

    // Decrement Network Activity Count
    [self decrementNetworkActivityCount];

    NSLog(@"Operation Will Terminate");
}

完成分段上传的方法将布尔值_operationShouldTerminate 设置为YES 以终止操作。那个方法看起来像这样。

- (void)finalizeMultipartUpload {
    // Notify Delegate
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.delegate operation:self didFinishUploadingUploadWithUuid:self.uuid];
    });

    // Operation Should Terminate
    _operationShouldTerminate = YES;

    NSLog(@"Finalize Multipart Upload");
}

最终的日志语句被打印到控制台,但是 main 方法中的 while 循环似乎没有退出,因为操作的 main 方法中的最终日志语句没有打印到控制台。结果,调度操作所在的操作队列被阻塞,任何调度的操作都不会执行。

操作的isFinished 方法只返回_operationShouldTerminate,如下所示。

- (BOOL)isFinished {
    return _operationShouldTerminate;
}

奇怪的是,while 循环没有退出,更奇怪的是它没有在我自己的任何测试设备(iPhone 3GS、iPad 1 和 iPad 3)上发生。非常感谢任何帮助或指示。

【问题讨论】:

    标签: ios multithreading amazon-s3 nsoperation nsoperationqueue


    【解决方案1】:

    事实证明,问题的解决方案既复杂又简单。我错误地认为操作的方法和委托回调是在同一个线程上执行的,即调用操作的main 方法的线程。情况并非总是如此。

    尽管这在我的测试和我的设备 (iPhone 3GS) 上是正确的,这就是我自己没有遇到问题的原因。然而,我的 beta 测试人员使用的设备具有多核处理器 (iPhone 4/4S),这导致某些代码在与调用操作的 main 方法的线程不同的线程上执行。

    这样做的结果是_operationShouldTerminate在错误线程上的finalizeMultipartUpload方法中被修改了。这反过来意味着main方法的while循环没有正确退出导致操作死锁。

    简而言之,解决方案是在调用main 方法的同一线程上更新_operationShouldTerminate。这将正确退出while 循环并退出操作。

    【讨论】:

      【解决方案2】:

      您的代码存在许多问题,我可以提供两种解决方案:

      1) 阅读 Apple 的 Concurrency Programming Guide 中的并发 NSOperations。要使 runLoop 保持“活动”,您必须添加一个端口或安排一个计时器。主循环应该包含一个自动释放池,因为您可能没有得到一个(请参阅同一备忘录中的内存管理)。您需要实现 KVO 以让 operationQueue 知道您的操作何时完成。

      2) 或者,您可以采用small amount of field tested hardened code 并重复使用它。该 Xcode 项目包含您感兴趣的三个类:一个 ConcurrentOperation 文件,它可以很好地完成您在上面尝试完成的工作。 Webfetcher.m 类展示了如何对并发操作进行子类化以从 Web 执行异步 URL 获取。 OperationsRunner 是一个小的帮助文件,您可以添加到任何类型的类中来管理操作队列(运行、取消、查询等)。以上所有代码都少于 100 行,为您的代码工作提供了基础。 OperationsRunner.h 文件也提供了“如何做”。

      【讨论】:

      • 感谢您的回答大卫。并发编程指南对我来说并不陌生,并且确实是解决此类问题的有用指南。在这种情况下,让运行循环保持活动状态似乎不是问题。
      猜你喜欢
      • 1970-01-01
      • 2015-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-05
      • 1970-01-01
      • 1970-01-01
      • 2015-10-30
      相关资源
      最近更新 更多