【问题标题】:Managing CPU intensive threads on iOS在 iOS 上管理 CPU 密集型线程
【发布时间】:2011-09-13 21:58:53
【问题描述】:

我是一位经验丰富的 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 发送消息,告诉它更新和/或检查按钮的状态吗?

感谢您的支持。

【问题讨论】:

    标签: iphone objective-c ios multithreading nsoperation


    【解决方案1】:

    1.) NSOperation 队列是一个合理的选择吗?如果没有,还有什么?

    NSOperationQueue 听起来很合理。

    当然,您可以选择:pthreads、libdispatch(又名 GCD)、建立在 pthreads 之上的 c++ 线程库等,等等。你喜欢的模特。

    2.) 如何减少睡眠? (显然我希望工作获得尽可能多的周期/合理的,而且我不确定我的睡眠是否对所有要更新的 UI 做任何事情。)

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

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

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

    另一种选择对事件或阶段更有用:它将涉及随着进度的进行从辅助线程发布更新(例如通知或对委托的回调)(更多信息在 #2 下)。

    更新

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

    是的,这很好 - 您可以采取许多方法。哪个是“最好的”取决于上下文。

    我目前的理解是在一个线程中启动 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
    

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

    【讨论】:

    • 我习惯于按照你在最后两段中描述的方式做事。我不确定这在 iOS 模型中是否合适,但听起来确实如此。我目前的理解是在一个线程中启动 UI(不是主线程!),启动我的工作线程,使用计时器向 UI 生成信号以查看进度值并适当地更新进度条。出于这个特定应用程序的目的,你的倒数第二段已经足够了,我不需要深入到最后一段的长度(至少现在是这样)。谢谢。
    【解决方案2】:

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

    如果它们在主线程上运行,您可以检查任何更新 UI 的方法,例如

    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... 
    });
    

    【讨论】:

    • 感谢您的断言!请为我澄清一些事情。预计我在主线程上运行我的 UI,然后工作线程不在主线程上......或者......我是否也让 UI 离开主线程?我将您的评论解释为 UI 应该在主线程上,但我想确定一下。
    • 您必须从主线程更新您的 UI。请注意,NSOperationQueue 中的操作通常在主线程上执行,因此如果您从操作中更新 UI,则必须在主线程/队列上显式执行此操作(请参阅我的第二个代码 sn-p)。
    • 完美,我想我现在明白了。谢谢。
    【解决方案3】:

    这是我对你问题的回答。

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

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

    3) 当您需要更新 UI 时,只需在主队列上调度(在同一块内完成工作,使用 dispatch_get_main_queue() )根据需要更新 UI。

    查看相关的 GCD 文档here

    【讨论】:

    • 是的,我习惯了线程。我知道 NSOPeration 是建立在 GCD 之上的,并且只是一个简单的入口。但如果有意义的话,我很乐意使用另一个模型。我已经浏览了 GCD 文档(感谢您的指点),并且我很乐意自己进行调度。 #2 和 #3 是我习惯的模型(来自 Windows),我很高兴走这条路。谢谢。
    【解决方案4】:

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

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

    【讨论】:

    • 太好了,这是我将使用的模型(其他答案也加强了。谢谢大家的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-11
    • 2020-06-16
    • 2012-09-24
    • 1970-01-01
    • 2021-08-15
    • 1970-01-01
    相关资源
    最近更新 更多