3

I use a subclass of NSOperation to upload large files to AWS S3 using Amazon's iOS SDK (v1.3.2). This all works fine, but some beta testers experience deadlocks (iOS 5.1.1). The result is that the NSOperationQueue in which the operations are scheduled is blocked as only one operation is allowed to run at one time. The problem is that I cannot reproduce the issue whereas the beta testers experience this problem every single time.

The operation is quite complex due to how the AWS iOS SDK works. However, the problem is not related to the AWS iOS SDK as far as I know based on my testing. The operation's main method is pasted below. The idea of the operation's main method is based on 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");
}

The method that finalizes the multipart upload sets the boolean _operationShouldTerminate to YES to terminate the operation. That method looks like this.

- (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");
}

The final log statement is printed to the console, but the while loop in the main method does not seem to exit as the final log statement in the operation's main method is not printed to the console. As a result, the operation queue in which the operation is scheduled, is blocked and any scheduled operations are not executed as a result.

The operation's isFinished method simply returns _operationShouldTerminate as seen below.

- (BOOL)isFinished {
    return _operationShouldTerminate;
}

It is odd that the while loop is not exited and it is even more odd that it does not happen on any of my own test devices (iPhone 3GS, iPad 1, and iPad 3). Any help or pointers are much appreciated.

4

2 回答 2

2

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

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

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

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

于 2012-09-12T10:04:29.760 回答
1

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

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

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

于 2012-09-11T18:00:15.773 回答