NSOperation死锁并阻止NSOperationQueue

时间:2012-09-11 12:22:04

标签: ios multithreading amazon-s3 nsoperation nsoperationqueue

我使用NSOperation的子类使用亚马逊的iOS SDK(v1.3.2)将大文件上传到AWS S3。这一切都很好,但一些beta测试者遇到死锁(iOS 5.1.1)。结果是,计划操作的NSOperationQueue被阻止,因为一次只允许一个操作运行。问题是我不能重现这个问题,而beta测试者每次都会遇到这个问题。

由于AWS iOS SDK的工作原理,操作非常复杂。但是,据我所知,根据我的测试,该问题与AWS iOS SDK无关。操作的主要方法粘贴在下面。操作的主要方法的想法基于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)上。非常感谢任何帮助或指示。

2 个答案:

答案 0 :(得分:2)

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

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

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

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

答案 1 :(得分:1)

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

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文件也提供了“怎么办”。