从 NSOperationQueue 取消 NSOperation 导致崩溃

发布于 2025-01-07 22:57:06 字数 2288 浏览 0 评论 0原文


我正在尝试构建一个下载管理器类,它将所有异步下载(每个操作都有自己的线程)操作打包到 NSOperation 子类中,以便稍后将它们添加到 NSOperationQueue 中。下载管理器类(单例)还公开了一些方法来处理队列并取消符合某些要求的操作。
这些是开始创建类集群(抽象工厂)的步骤,该类集群为不同类型的常见操作(上传、下载、解析等)返回不同类型的 NSOperation。
该类似乎可以很好地处理下载操作,但如果在这些操作中间我调用取消操作的方法,则该操作会成功取消,但应用程序会在稍后的几个操作中崩溃。如果我不取消任何操作,一切都会正常。所有操作均使用 KVO 进行观察。 删除操作的方法看起来像这样:

- (void) cancelDownloadOperationWithID:(NSString *)aUUID{
@synchronized(self){
    [self.dowloadQueue setSuspended:YES]; //downloadQueue is an NSOperationQueue
    NSArray * downloadOperations = [self.dowloadQueue operations];
    NSPredicate * aPredicate = [NSPredicate predicateWithFormat:@"SELF.connectionID == %@",aUUID]; //SELF is the signleton instance of the download manager
    NSArray * filteredArray = [downloadOperations filteredArrayUsingPredicate:aPredicate];
    if ([filteredArray count]==0) {
        [self.dowloadQueue setSuspended:NO];
        return;
    } 
    [filteredArray makeObjectsPerformSelector:@selector(cancel)];
    NSLog(@"Cancelled %d operations",[filteredArray count]);
    [self.dowloadQueue setSuspended:NO];
   }
}

崩溃日志非常难以理解,但是是一个 BAD_EXC_ACCESS (可能是僵尸),请注意我处于 ARC 下。

0x00a90ea8  <+0393>  jle    0xa90d9f <____NSOQSchedule_block_invoke_0+128>
0x00a90eae  <+0399>  mov    -0x38(%ebp),%ecx
0x00a90eb1  <+0402>  mov    -0x34(%ebp),%esi
0x00a90eb4  <+0405>  mov    (%esi,%ecx,1),%ecx
0x00a90eb7  <+0408>  mov    -0x40(%ebp),%esi
0x00a90eba  <+0411>  cmpb   $0x0,(%ecx,%esi,1)
0x00a90ebe  <+0415>  jne    0xa90d9f <____NSOQSchedule_block_invoke_0+128>
0x00a90ec4  <+0421>  mov    (%edi,%eax,1),%esi
0x00a90ec7  <+0424>  mov    (%esi,%edx,1),%ebx
0x00a90eca  <+0427>  mov    %ebx,-0x2c(%ebp)
0x00a90ecd  <+0430>  mov    -0x44(%ebp),%ebx
0x00a90ed0  <+0433>  cmpl   $0x50,(%esi,%ebx,1)
0x00a90ed4  <+0437>  mov    %edi,%ebx
0x00a90ed6  <+0439>  jne    0xa90e96 <____NSOQSchedule_block_invoke_0+375>
0x00a90ed8  <+0441>  mov    -0x48(%ebp),%ebx
0x00a90edb  <+0444>  cmpb   $0x0,(%esi,%ebx,1)
0x00a90edf  <+0448>  mov    %edi,%ebx
0x00a90ee1  <+0450>  je     0xa90e96 <____NSOQSchedule_block_invoke_0+375>

有人能给我建议吗?
感谢安德里亚

I'm trying to build a download manager class that packets all the async download (every op has its own thread) operation in an NSOperation subclass to add them later in an NSOperationQueue. The download manager class (a singleton) also exposes few methods to work on the queue and cancel operations that matches some requirements.
Those are steps to start creating kind of a Class Cluster(Abstract Factory) that returns different kinds of NSOperation for different types of common operation (upload, download,parse, etc).
The class seems to work pretty well with download operations, but if in the middle of those operations I call a method for cancel an operation, the operation is successfully cancelled but the app crashes few operation later. If I don't cancel any operations everything works fine. All operations are observed using KVO.
The method that remove the operation looks like that:

- (void) cancelDownloadOperationWithID:(NSString *)aUUID{
@synchronized(self){
    [self.dowloadQueue setSuspended:YES]; //downloadQueue is an NSOperationQueue
    NSArray * downloadOperations = [self.dowloadQueue operations];
    NSPredicate * aPredicate = [NSPredicate predicateWithFormat:@"SELF.connectionID == %@",aUUID]; //SELF is the signleton instance of the download manager
    NSArray * filteredArray = [downloadOperations filteredArrayUsingPredicate:aPredicate];
    if ([filteredArray count]==0) {
        [self.dowloadQueue setSuspended:NO];
        return;
    } 
    [filteredArray makeObjectsPerformSelector:@selector(cancel)];
    NSLog(@"Cancelled %d operations",[filteredArray count]);
    [self.dowloadQueue setSuspended:NO];
   }
}

The crash log is pretty incomprehensible but is a BAD_EXC_ACCESS (a zombie perhaps), notice that I'm under ARC.

0x00a90ea8  <+0393>  jle    0xa90d9f <____NSOQSchedule_block_invoke_0+128>
0x00a90eae  <+0399>  mov    -0x38(%ebp),%ecx
0x00a90eb1  <+0402>  mov    -0x34(%ebp),%esi
0x00a90eb4  <+0405>  mov    (%esi,%ecx,1),%ecx
0x00a90eb7  <+0408>  mov    -0x40(%ebp),%esi
0x00a90eba  <+0411>  cmpb   $0x0,(%ecx,%esi,1)
0x00a90ebe  <+0415>  jne    0xa90d9f <____NSOQSchedule_block_invoke_0+128>
0x00a90ec4  <+0421>  mov    (%edi,%eax,1),%esi
0x00a90ec7  <+0424>  mov    (%esi,%edx,1),%ebx
0x00a90eca  <+0427>  mov    %ebx,-0x2c(%ebp)
0x00a90ecd  <+0430>  mov    -0x44(%ebp),%ebx
0x00a90ed0  <+0433>  cmpl   $0x50,(%esi,%ebx,1)
0x00a90ed4  <+0437>  mov    %edi,%ebx
0x00a90ed6  <+0439>  jne    0xa90e96 <____NSOQSchedule_block_invoke_0+375>
0x00a90ed8  <+0441>  mov    -0x48(%ebp),%ebx
0x00a90edb  <+0444>  cmpb   $0x0,(%esi,%ebx,1)
0x00a90edf  <+0448>  mov    %edi,%ebx
0x00a90ee1  <+0450>  je     0xa90e96 <____NSOQSchedule_block_invoke_0+375>

Can some one give me suggestion about it?
Thanx Andrea

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

画离情绘悲伤 2025-01-14 22:57:06

答案很简单。在 NSOperation 子类的重写 -cancel 方法中,我设置了已完成的变量和正在执行的变量,以触发正确的 KVO 回调。问题是,即使取消操作,操作也会保留在 NSOperationQueue 中,当队列尝试在已触发其 KVO 回调的 NSOperationQueue 上启动 -start 方法时,它会崩溃。

解决方法如下:如果操作在未执行时被取消,则必须在执行 start 方法后立即将 finish 变量设置为 YES,否则如果正在执行,则可以将 finish 设置为 YES 并将执行设置为 NO。

Well the answer was pretty simple. In the overridden -cancel method of the NSOperation subclass I was setting both the finished and executing vars triggering the proper KVO callbacks. The problem is that an operation stays in the NSOperationQueue even if it is cancelled, when the queue tries to launch the -start method on an NSOperationQueue that has triggered its KVO callback it crashes.

The work around is as follows: If the operation was cancelled while it was not executing, you must set the finish var to YES right after the start method implementation, otherwise if it was executing it's ok to set finished to YES and executing to NO.

汹涌人海 2025-01-14 22:57:06

接受的答案对我有用。为了帮助解决这个问题,以防其他人遇到这个问题,在异步操作开始执行之前,我在 -cancel 内部不正确地设置了 isFinished ,也经历了这次崩溃。

我没有这样做,而是将 - cancel 切换为仅在操作已经 isExecuting 时更改 isFinished,然后在 - start< /code> 我按照此处的建议立即设置了 isFinished 。瞧,崩溃消失了。

The accepted answer works for me. Just to help clear this up in case anybody else runs into it, I also experienced this crash by setting isFinished improperly inside of my - cancel, before an async operation had begun executing.

Rather than doing that, I switched my - cancel to only change isFinished if the operation was already isExecuting, then in - start I set isFinished immediately as suggested here. Voilà, crash gone.

网名女生简单气质 2025-01-14 22:57:06

这是使用之前两个答案的 swift 文章:

override func cancel() {
    super.cancel()

    if executing {
        executing = false
        finished = true
    }

    task.cancel()
}

override func start() {
    if cancelled {
        finished = true
        return
    }

    executing = true

    main()
}

override func main() {
    task.resume()
}

Here's a piece in swift using two previous answers:

override func cancel() {
    super.cancel()

    if executing {
        executing = false
        finished = true
    }

    task.cancel()
}

override func start() {
    if cancelled {
        finished = true
        return
    }

    executing = true

    main()
}

override func main() {
    task.resume()
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文