【问题标题】:NSTimer sometimes freezes when app is doing heavy computation当应用程序进行大量计算时,NSTimer 有时会冻结
【发布时间】:2012-01-09 10:30:31
【问题描述】:

我想在应用程序在后台进行一些计算时为一些加载点设置动画。我通过NSTimer 实现了这一点:

    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.3f
                                             target:self 
                                           selector:@selector(updateLoadingPoints:) 
                                           userInfo:nil 
                                            repeats:YES];

不幸的是,有时,当计算变得非常繁重时,该方法不会被触发,因此不会发生更新。似乎所有的触发都在一个队列中,在繁重的计算之后触发​​。

有没有办法给NSTimer 更高的优先级以确保它定期调用我的方法?或者有其他方法可以实现吗?

【问题讨论】:

  • 计时器和其他计算是否在相同线程上?
  • @JoshCaswell 不,它们在不同的线程上。

标签: objective-c multithreading cocoa-touch ios5 heavy-computation


【解决方案1】:

NSTimer 通过在主运行循环的队列中添加事件来工作;它与用于触摸事件和 I/O 数据接收事件等的事件队列相同。您设置的时间间隔不是一个精确的时间表;基本上在每次通过运行循环时,都会检查计时器以查看是否要触发任何计时器。

由于它们的实现方式,无法提高计时器的优先级。

听起来您的辅助线程占用了主线程的大量 CPU 时间,因此计时器不会像您希望的那样频繁触发。换句话说,主线程饿死 CPU 时间。

调用performSelectorOnMainThread: 不一定有帮助,因为这些方法本质上是在主线程的事件队列中添加一个单次触发计时器。因此,您只需以不同的方式设置计时器。

为了解决您的问题,我建议您通过降低计算线程的优先级来增加主线程的相对优先级。 (见[NSThread setThreadPriority:]。)

让重要的工作线程以低于主线程的优先级运行似乎违反直觉,主线程只是将内容绘制到屏幕上,但在人性化的应用程序中,保持屏幕最新并响应用户输入通常是应用程序应该做的最重要的事情。

实际上,主线程只需要很少的 CPU,因此它不会真正减慢您的工作线程;相反,您只是确保在主线程需要做某事的一小段时间内,它可以快速完成。

【讨论】:

    【解决方案2】:

    定时器被添加到它被调度的运行循环中。如果您在辅助线程(例如您的工作线程)上创建计时器,则很有可能您也将它安排在辅助线程上。

    您希望主线程上的 UI 更新。因此,您希望将计时器安排在主线程上。如果您的更新仍然很慢,也许您的主线程可以做更少的工作,并确保您的线程数量非常少,并且您正在适当地锁定。

    我怀疑你是在一个辅助线程上创建的,它没有像计时器想要触发的那样频繁地运行运行循环。如果它在后台做很多(长时间的)工作,并且没有运行运行循环,那么计时器将没有机会触发,因为当它的线程仍在处理时,消息没有机会被触发.

    【讨论】:

      【解决方案3】:

      从单独的线程而不是主线程调用计时器。这肯定会使其与其他主线程的处理分开,从而为您提供所需的结果。

      【讨论】:

      • 我的处理发生在后台,而不是主线程。在我的计时器方法中,我需要更新 GUI,所以 updateLoadingPoints: 需要在主线程上做一些事情。如果我尝试在后台触发计时器,不幸的是我的问题没有解决。
      • 如果您在后台启动它,您始终可以使用 CGD。 dispatch_sync(dispatch_get_main_queue(), ^ { //更新 GUI });
      • 您必须使用 perfromSelectorOnMainThread,这将再次要求主线程为您的 UI 更新免费。您可以做的是您可以将 UI 视图传递给计时器方法中的方法,并直接从方法中更新 UI。这将解决等待主线程的问题。祝你好运!
      • @Eugene 我正在这样做。
      【解决方案4】:

      使用 performSelectorInBackground:withObject 在单独的线程上执行计算。在你的 UI 循环中总是尽可能少做,因为这里所做的任何工作都会阻止鼠标点击,导致 SPoD/沙滩球,并延迟计时器处理程序。

      我怀疑不仅仅是您的 TIMER 没有响应,而是整个 UI 都没有响应。

      很抱歉在我的早期版本中调用了错误的 API - 我的复制/粘贴失败。

      【讨论】:

      • -performSelectorOnMainThread:withObject:waitUntilDone: 将在主(即 UI)线程上执行选择器。由于计时器需要运行循环,并且大多数应用程序只需要在主线程上配置运行循环,因此这基本上不会完成任何事情。你想要-performSelectorInBackground:withObject:
      • Jonathan - 你是对的,我复制了错误的 API。我已经更新了我的回复。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-01-31
      • 1970-01-01
      • 2011-07-08
      • 1970-01-01
      • 1970-01-01
      • 2022-01-10
      相关资源
      最近更新 更多