在iOS上管理CPU密集型线程

我是一位经验丰富的C / C ++程序员,正在加速iPhone上的Objective C。 我做了很多搜索,但没有找到一个令人满意的答案,一定是一个常见的问题; 如果在其他地方得到解答,我道歉,指点将不胜感激。

我的应用程序非常占用CPU。 UI具有显示进度的简单显示和开始/停止按钮。 分配最多CPU周期以完成工作的最佳方法是什么,同时仍然确保显示器定期更新并且启动/停止按钮响应? 我已经读过你不应该在主线程中工作,但除此之外我还没有找到很多建议。 鉴于此,我已经在NSOperation队列中实现了我的工作。 我还将屏幕刷新放在自己的队列中。 我还用NSThread sleepForTimeIntervals自由地散布了代码。 我已经尝试了从.001到1的不同睡眠时间(例如[NSThread sleepForTimeIntervals .1])。 尽管如此,屏幕显示最多是缓慢(10秒)并按下停止按钮突出显示按钮但是没有任何事情再次发生10秒。

1.)NSOperation队列是否合理? 如果没有,还有什么? 2.)我如何最小化睡眠? (显然我希望工作能够获得尽可能多的周期/合理,并且我不确定我的睡眠对所有要更新的UI都做了什么。)3。)是否有更好的技术来保持UI最新? 例如,我可以使用NSTimer或其他方法向UI发送消息,告诉它更新和/或检查按钮的状态吗?

感谢您的支持。

1.)NSOperation队列是否合理? 如果没有,还有什么?

NSOperationQueue听起来很合理。

当然,你有选择:pthreads,libdispatch(又名GCD),构建在pthreads之上的c ++线程库等等,如果你没有产生太多/很多,那么它只是归结为你喜欢的模型。

2.)我如何最小化睡眠? (显然我希望工作能够获得尽可能多的周期/合理,并且我不确定我的睡眠对所有要更新的UI都做了什么。)

不要睡觉=)你可以使用你的ui元素的计时器或显式回调或通知来通知依赖。 如果依赖项执行ui更新,那么您可能会将消息添加到主线程的消息队列中。

3.)是否有更好的技术来保持UI的最新状态? 例如,我可以使用NSTimer或其他方法向UI发送消息,告诉它更新和/或检查按钮的状态吗?

这真的取决于你在做什么。 如果您只想更新进度条,则可以从辅助线程写入值并从主线程中读取值。 然后在主运行循环上使用计时器定期向对象发送消息以更新其显示(基于当前值)。 对于像未分级进度指示器这样的东西,这可能是好的。

另一个替代方案对于事件或阶段更有用:它将涉及在进行过程时从辅助线程发布更新(例如,向委托发送通知或回调)(#2下的更多信息)。

更新

我不确定这在iOS模型中是否合适,但听起来确实如此。

是的,那很好 – 你可以采取许多appraches。 “最好的”取决于具体情况。

我目前的理解是在一个线程(不是主要的!)中启动UI,

你真的没有明确启动UI; 通常通过将事件和消息推送到主线程来驱动主线程。 主线程使用运行循环并在运行循环的每次迭代中处理排队的消息/事件。 您也可以在将来安排这些消息(稍后会详细介绍)。 话虽如此,你对UIKit和AppKit(如果你的目标是osx)对象的所有消息都应该在主线程上(作为一种概括,你最终会知道它有例外)。 如果你有一个特定的实现,它与消息传递UIKit对象的方法完全分开,并且该程序是线程安全的,那么你可以从任何线程实际执行这些消息,因为它不会影响UIKit实现的状态。 最简单的例子:

@interface MONView : UIView @end @implementation MONView // ... - (NSString *)iconImageName { return @"tortoise.png"; } // pure and threadsafe @end 

启动我的工作线程,使用计时器为UI生成信号,以查看进度值并适当更新进度条。 出于这个特定应用的目的,你的倒数第二段是充足的,我不需要去最后一段的长度(至少现在)。 谢谢。

为此,您可以使用类似于此的方法:

 @interface MONView : UIView { NSTimer * timer; MONAsyncWorker * worker; // << this would be your NSOperation subclass, if you use NSOperation. } @end @implementation MONView // callback for the operation 'worker' when it completes or is cancelled. - (void)workerWillExit { assert([NSThread isMainThread]); // call on main // end recurring updates [self.timer invalidate]; self.timer = nil; // grab what we need from the worker self.worker = nil; // update ui } // timer callback - (void)timerUpdateCallback { assert([NSThread isMainThread]); // call on main assert(self.worker); double progress = self.worker.progress; [self updateProgressBar:progress]; } // controller entry to initiate an operation - (void)beginDownload:(NSURL *)url { assert([NSThread isMainThread]); // call on main assert(nil == worker); // call only once in view's lifetime // create worker worker = [[MONAsyncWorker alloc] initWithURL:url]; [self.operationQueue addOperation:worker]; // configure timer const NSTimeInterval displayUpdateFrequencyInSeconds = 0.200; timer = [[NSTimer scheduledTimerWithTimeInterval:displayUpdateFrequencyInSeconds target:self selector:@selector(timerUpdateCallback) userInfo:nil repeats:YES] retain]; } @end 

请注意,这是一个非常原始的演示。 将计时器,更新处理和操作放在视图的控制器中而不是视图中也更常见。

你在主线程上进行UI更新吗? 这非常重要,因为UIKit不是线程安全的,并且从辅助线程使用它可能导致行为迟缓(或者崩溃)。 您通常不需要在后台线程/队列中使用sleep来使UI保持响应(除非您的UI本身非常占用CPU,但这似乎不是这种情况)。

您可以检查更新UI的任何方法,如果它们在主线程上运行时具有类似的function

 NSAssert([NSThread isMainThread], @"UI update not running on main thread"); 

将UI更新与主线程同步的简单轻量方法是使用Grand Central Dispatch:

 dispatch_async(dispatch_get_main_queue(), ^ { //do your UI updates here... }); 

在这里,您可以回答我的问题。

1)由于您是一位经验丰富的C程序员,因此您会对Grand Central Dispatch(GCD)感到满意,这是一种基于C的并发API。

2)使用GCD,您根本不需要睡觉。 只需使用最大优先级(DISPATCH_QUEUE_PRIORITY_HIGH)异步调度您需要在队列中完成的工作。

3)当您需要更新UI时,只需根据需要在主队列(在同一个块中执行工作,使用dispatch_get_main_queue())调度UI更新。

请在此处查看相关的GCD文档。

我有一个模型对象来执行CPU任务,它具有输出更改时的委托回调和视图控制器。 在viewDidLoad ,将视图控制器设置为模型的委托。 因此,当计算的数据已更新时,模型可以使用线程并在主队列上发回消息。 除非您的案例特别复杂,否则只需使用Grand Central Dispatch并将密集型任务调度发送到另一个线程。

当然,你不应该在任何地方调用sleepForTimeInterval来实现你想要的。