调试间歇性卡顿的NSOperationQueue

4
我有一个iOS应用程序,存在一个非常严重的错误:NSOperationQueue中的某个操作会因某种原因挂起而无法完成执行,因此其他额外的操作正在排队等待但仍未执行。这反过来导致应用程序无法执行关键功能。除了它每周左右会在我的同事设备上发生之外,我尚未能够确定任何模式。此时从Xcode运行应用程序并不能帮助解决问题,因为杀死和重新启动应用程序可以暂时解决问题。我尝试将调试器附加到运行进程上,似乎能够看到日志数据,但我添加的任何断点都没有注册。我已经添加了一系列NSLogs来试图确定它挂起的位置,但这还没有导致解决方案。
我最初在另一个问题中描述了这个错误,该问题尚未得到明确的答案,我猜测是因为我能够提供的信息不足。
一个朋友曾经告诉我,在某个时刻可以以某种形式保存应用程序的整个内存堆栈,并将该内存状态重新加载到不同设备上的进程中。有人知道我如何实现这一点吗?如果可能,下次有人遇到这个错误,我可以保存该内存状态并复制以测试所有可能解决方案的理论。或者有没有其他方法来解决这个问题?作为临时措施,您认为在应用程序进入此状态时强制使应用程序崩溃是否有意义,以便实际用户会更少困惑?我对此有些矛盾,但用户无论如何都必须从多任务停靠栏中终止应用程序才能再次使用应用程序。我可以检查操作队列计数或创建某种超时代码,直到我真正解决了这个错误。

尝试将调试器附加到现有的运行进程上,而不是运行一个新实例。 - Simon Lee
啊,好的,抱歉,设备上的构建需要是调试版本,并且与您本地运行的代码库完全相同。这种情况是吗? - Simon Lee
我不确定在将断点附加到进程时是否应该起作用,这是可能的吗? - Jiho Kang
完全可以,假设您已经从计算机安装了调试版本(且代码库相同),只需将调试器附加到现有实例即可。 - Simon Lee
我刚刚测试了一下,但好像不起作用。我进行了全新的构建,停止了操作,手动在我的手机上启动了应用程序,然后尝试附加调试器。我收到了两个警告:“无法读取blahblah的符号”“本地找不到blahblah的副本,在远程设备上从内存中读取。这可能会减慢调试会话。” 我无法使用断点,NSLog也似乎没有显示出来。我做错了什么吗?我正在使用xcode 4.2,并通过Product> Attach Process然后从列表中选择正在运行的应用程序进行附加。 - Jiho Kang
显示剩余5条评论
5个回答

4
这听起来像是一个非常罕见的竞争条件下的死锁。您还提到使用了maxConcurrentOperationCount为2。这意味着:
  1. 某些操作正在阻塞操作队列,等待主要线程释放某个锁,并且主线程正在等待该操作完成
  2. 两个操作正在等待对方释放某个锁
情况1似乎非常不可能发生,因为队列应该允许2个并发操作被完全阻塞,除非您正在使用一些具有并发问题的系统函数,它会阻塞您的队列而不仅仅是一个线程。
在这种情况下,我的第一次尝试调试将是连接调试器并暂停执行。然后您可以查看所有线程的堆栈跟踪。您应该能够找到由操作队列创建的两个线程,之后我将检查负责的函数以查找可能等待某个锁的代码。请确保考虑到系统函数。

我曾经遇到过同样的问题 - 经过仔细检查发现,实用程序代码的某些部分具有互斥条件 - 这在主线程代码和另一个线程上运行的某个操作中都使用了。这导致两者相互等待,从而死锁。使用其他安全逻辑删除了互斥逻辑,现在它可以正常工作了。 - Ramesh

1

先检查一下假设,因为这从来不会有坏处:你是否确实有证据表明你的后台线程挂起了?根据你所报告的情况,观察到的行为是你放在后台线程中的任务没有达到你预期的结果。这并不一定意味着线程已经挂起——它可能只是表明特定条件下线程关闭了,因为所有任务都已完成,但任务没有达到你想要的目标。

补充:根据你在评论中的回答,我认为下一步应该使用日志记录队列中开始执行项的时间,以便您可以确定导致队列阻塞的是哪些项。最好的猜测是,如果它们都属于某个类,则是某些项目的某个类或某些项目的某些特征。在执行每个项目的第一步时记录足够的日志,以便您可以合理地描述该项目,然后一旦您获得了进入此状态的真实设备,请检查日志并查看导致此问题的条件。这应该使您能够在调试期间或模拟器中可靠地重现设备上的问题,以便随后解决它。

换句话说,我建议您首先将注意力集中在识别有问题的操作上,而不是试图确定代码中哪一行出现了停顿。


NSLog显示,当应用程序陷入此“状态”时,新的NSOperations被添加到NSOperationQueue和队列中,但计数仍在增加而不是减少,如果操作正在成功运行和完成,则应该是这种情况。我的NSOperationQueue的maxConcurrentOperationCount为2,因此如果有2个未完成的操作,则排队的操作计数将继续递增,这正是我遇到的问题。 - Jiho Kang

1

你没有说,但我猜想这个 bug 是在人工操作应用程序时发生的?也许你应该为这个应用程序添加一个自动模式,在这个模式下,应用程序模拟用户通常执行的相同操作,并使用随机时间启动不同的操作。然后,你可以让应用程序在所有设备上无人值守运行,增加发现问题的机会。

此外,由于问题似乎与 NSOperationQueue 相关,也许你应该对其进行子类化,以便可以向更有趣的方法添加日志记录。例如,每次添加操作时,你应该记录队列的状态,因为你怀疑有时它会被暂停。

另外,我在你的其他问题中也建议过,你可能需要设置观察者来获取通知,如果队列进入暂停状态。

祝好运。


谢谢,这是一个非常好的想法,但我还是有些怀疑能否通过这种方式捕捉到错误,因为在将近一个半月的时间里,我从未能够手动重现这个问题。我的应用程序更多地是一个过于复杂的文本消息应用程序,用户行为包括在一天内多次关闭和打开应用程序,从推送通知中进入,而且我还必须考虑到较差的3G或EDGE连接。现在,我倾向于在相关代码的所有可能点添加日志记录代码。这不是一个坏主意,只是很痛苦。 - Jiho Kang

1

解决不会导致应用程序崩溃,但会挂起线程的错误相当困难。如果您无法通过逐步查看代码并检查是否存在任何可能的死锁或竞争条件来找到错误,则建议实现一些日志记录。

每次添加日志条目时将日志写入磁盘。这不是最节省内存的方法,但如果您向同事提供启用了日志记录的构建,则可以在出现问题时从他的iPhone中提取日志。即使应用程序仍在运行。

确保记录您采取的每个步骤,包括您怀疑会破坏应用程序的代码周围重要变量的值。这样,您就可以看到应用程序正在做什么以及应用程序的状态。

希望这有点帮助。我不知道如何恢复应用程序的内存状态,所以无法为您提供帮助。

注意:如果应用程序在错误上崩溃,您可以使用其他工具,但如果我理解正确,这里不是这种情况,对吧?


我阅读了描述错误的问题,并尝试将当前正在运行的操作记录到磁盘中。似乎有时候操作会挂起,其中存在一个错误。如果您可以记录运行操作时调用的方法,这将显示哪个函数调用会使应用程序挂起,然后您可以开始查找。


感谢您的输入。是的,这个特定的错误不会导致应用程序崩溃,但只会使我的一些线程挂起。应用程序在主线程上执行的所有操作都正常运行,但需要在后台线程中运行的关键功能不会运行,因此用户认为应用程序没有响应。 - Jiho Kang

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接