在对UI响应的永无止境的追求中,我想更多地了解主线程执行阻止操作的情况.

我正在寻找某种“调试模式”或额外的代码,或钩子,或任何东西,从而我可以设置一个断点/日志/将被击中的东西,并允许我检查如果我的主要线程“自愿”用于I / O的块(或任何原因,真的),除了在循环结束时空闲.

在过去,我已经使用循环观察器观察了跑步循环的时钟周期,这对于查看问题很有价值,但是在你可以检查的时候,为了做一个好主意,为时已晚,因为您的代码已经在该循环的runloop中运行了.

我知道UIKit / AppKit执行的操作是仅主线程,这将导致I / O,并导致主线程阻塞,在一定程度上,它是无望的(例如,访问粘贴板似乎是一个潜在的阻止,仅限主线程的操作),但是一些东西比没有更好.

任何人有什么好的想法?似乎像有用的东西.在理想的情况下,您永远不会阻止主线程,而您的应用程序的代码在运行环境中处于活动状态,像这样的操作将非常有助于尽可能接近该目标.

解决方法

所以我在这个周末提出回答我自己的问题.为了纪录,这项努力变成了一件相当复杂的事情,就像肯德尔·赫姆斯特特·格伦(Kendall Helmstetter Glen)所提出的那样,大多数阅读这个问题的人都应该很乐意与仪器混淆.对于群众中的狂热者,请继续阅读!

最重要的是重新解决问题.这是我想出的:

I want to be alerted to long periods of time spent in
syscalls/mach_msg_trap that are not legitimate idle time. “Legitimate
idle time” is defined as time spent in mach_msg_trap waiting for the
next event from the OS.

同样重要的是,我不在乎用户代码需要很长时间.这个问题很容易使用仪器的时间分析器工具进行诊断和了解.我想知道关于堵塞的时间.尽管您也可以使用Time Profiler来诊断被阻止的时间,但我发现用于此目的相当困难.同样,系统跟踪仪器对于这样的调查也是有用的,但是非常细小和复杂.我想要一些更简单的东西 – 更多地针对这个具体的任务.

看起来很明显,这里选择的工具是Dtrace.
我开始使用一个CFRunLoop观察者发射kcfRunLoopAfterWaiting和kcfRunLoopBeforeWaiting.对我的kcfRunLoopBeforeWaiting处理程序的调用将指示“合法空闲时间”的开始,并且kcfRunLoopAfterWaiting处理程序将是向我发出合法等待结束的信号.我将使用Dtrace pid提供程序来陷阱对这些函数的调用,作为排除合法空闲阻塞空闲的一种方法.

这种方法让我开始了,但最终证明是有缺陷的.最大的问题是许多AppKit操作是同步的,因为它们阻止了UI中的事件处理,但实际上在调用堆栈中旋转了RunLoop. RunLoop的这些旋转不是“合法的”空闲时间(为了我的目的),因为在此期间用户不能与UI进行交互.它们是有价值的,可以肯定的是,想象一下在后台线程上运行一个RunLoop面向I / O的runloop,但是当主线程发生这种情况时,UI仍然被阻止.例如,我将以下代码放入IBAction并从一个按钮触发:

NSMutableuRLRequest *req = [NSMutableuRLRequest requestWithURL: [NSURL URLWithString: @"http://www.google.com/"] 
                                                   cachePolicy: NSURLRequestReloadIgnoringCacheData
                                               timeoutInterval: 60.0];    
NSURLResponse* response = nil;
NSError* err = nil;
[NSURLConnection sendSynchronousRequest: req returningResponse: &response error: &err];

该代码不会阻止RunLoop旋转 – AppKit在sendSynchronousRequest:…调用中为您旋转 – 但它确实阻止用户与UI交互,直到它返回.这不是我的“合法闲置”,所以我需要一种方法来整理哪些空闲的东西. (CFRunLoopObserver方法也有缺陷,因为它需要修改代码,我的最终解决方案不会.)

我决定将我的UI /主线程模型化为状态机.在任何时候都处于三种状态之一:LEGIT_IDLE,RUNNING或BLOCKED,并且将在程序执行之间在这些状态之间来回切换.我需要提出Dtrace探测器,这样可以让我捕获(因此测量)这些转换.我实施的最终状态机比这三个状态要复杂得多,但这是20,000英尺的视图.

如上所述,从不良空闲中排除合法空闲并不简单,因为这两种情况最终都在mach_msg_trap()和__CFRunLoopRun中.我在调用堆栈中找不到一个简单的工件,我可以使用它来可靠地区分差异;看来,对一个功能的简单探测不会帮助我.我最终使用调试器来查看堆栈的状态,这些状态在合法空闲和不良空闲的各种情况下.我确定在合法闲置期间,我(看起来可靠)看到一个这样的调用堆栈:

#0  in mach_msg
#1  in __CFRunLoopServiceMachPort
#2  in __CFRunLoopRun
#3  in CFRunLoopRunSpecific
#4  in runcurrentEventLoopInMode
#5  in ReceiveNextEventCommon
#6  in BlockUntilNextEventMatchingListInMode
#7  in _DPSNextEvent
#8  in -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]
#9  in -[NSApplication run]
#10 in NSApplicationMain
#11 in main

所以我努力建立一堆嵌套/链接的pid探测器,当我到达并随后离开这个状态时,它将建立起来.不幸的是,无论什么原因,Dtrace的pid提供者似乎无法普遍检测所有任意符号的输入和返回.具体来说,我无法在pid000上找到探针:*:__ CFRunLoopServiceMachPort:return或on pid000:*:_ DPSNextEvent:return to work.细节并不重要,但是通过观察各种其他事情,跟踪某些状态,当我进入并离开合法闲置状态时,我能够(再次看似可靠地)建立起来.

然后我不得不确定探测器来告诉RUNNING和BLOCKED之间的区别.那有点简单最后,我选择考虑BSD系统调用(使用Dtrace的系统调用探针),并调用mach_msg_trap()(使用pid探针),而不是在合法空闲时段发生的BLOCKED. (我看过Dtrace的mach_trap探测器,但似乎没有做我想要的,所以我回到使用pid探针.)

最初,我与Dtrace sched提供商进行了一些额外的工作,以实际测量实际的阻塞时间(即我的线程被调度程序暂停的时间),但这增加了相当大的复杂性,我最终想到了自己:“如果我在内核中,如果线程实际上是睡着了,该怎么办?对用户来说都是一样的:它被阻止了.所以最后的方法只是在(syscalls || mach_msg_trap())&&&& !authorized_idle并且调用阻塞的时间.

在这一点上,捕获长持续时间的单个内核调用(例如调用sleep(5))变得微不足道.然而,更多的UI线程延迟来自于通过多次调用进入内核的许多小延迟(想到数百个对read()或select()的调用),所以我认为,当希望将SOME调用堆栈转储到事件循环的单次通过中的系统调用或mach_msg_trap时间的总量超出了一定的阈值.我结束了在各州设置各种计时器和记录累计时间,范围在州机器的各个州,当我们碰巧从BLOCKED状态转移出来时倾倒警报,并且已经超过了一个阈值.这种方法显然会产生可能会被误解的数据,或者可能是一个完整的红色鲱鱼(即一些随机的,相对较快的系统调用,恰好将我们超过了警戒阈值),但我觉得它比没有更好.

最后,Dtrace脚本最终将状态机保存在D变量中,并使用所描述的探针来跟踪状态之间的转换,并使我有机会在状态机转换状态时进行操作(如打印警报)在某些条件下.我玩了一个有创意的示例应用程序,做一堆磁盘I / O,网络I / O和调用sleep(),并能够抓住所有这三种情况,而不会分散与合法等待的数据.这正是我正在寻找的.

这个解决方案显然相当脆弱,几乎在各方面都非常糟糕. :)对我或任何其他人来说,这可能或不是有用的,但这是一个有趣的练习,所以我以为我会分享这个故事,以及由此产生的Dtrace脚本.也许别人会发现它很有用.我也必须承认在编写Dtrace脚本方面是一个相对的n00b,所以我确信我做了一百万个错误.请享用!

它太大了,不能排队,所以它由@Catfish_Man托管在这里:MainThreadBlocking.d

ios – 当我的主线程阻塞时,如何获得断点/日志/增加的可见性?的更多相关文章

  1. iOS:核心图像和多线程应用程序

    我试图以最有效的方式运行一些核心图像过滤器.试图避免内存警告和崩溃,这是我在渲染大图像时得到的.我正在看Apple的核心图像编程指南.关于多线程,它说:“每个线程必须创建自己的CIFilter对象.否则,你的应用程序可能会出现意外行为.”这是什么意思?我实际上是试图在后台线程上运行我的过滤器,所以我可以在主线程上运行HUD(见下文).这在coreImage的上下文中是否有意义?

  2. ios – 多个NSPersistentStoreCoordinator实例可以连接到同一个底层SQLite持久性存储吗?

    我读过的关于在多个线程上使用CoreData的所有内容都讨论了使用共享单个NSPersistentStoreCoordinator的多个NSManagedobjectContext实例.这是理解的,我已经使它在一个应用程序中工作,该应用程序在主线程上使用CoreData来支持UI,并且具有可能需要一段时间才能运行的后台获取操作.问题是NSPersistentStoreCoordinator会对基础

  3. ios – XCode断点应该只挂起当前线程

    我需要调试多线程错误.因此,为了获得生成崩溃的条件,我需要在代码中的特定点停止一个线程,并等待另一个线程到达第二个断点.我现在遇到的问题是,如果一个线程遇到断点,则所有其他线程都被挂起.有没有办法只停止一个线程,让其他线程运行,直到它们到达第二个断点?)其他更有趣的选择:当你点击第一个断点时,你可以进入控制台并写入这应该在该断点处暂停当前上下文中的线程一小时.然后在Xcode中恢复执行.

  4. ios – 在后台线程中写入Realm后,主线程看不到更新的数据

    >清除数据库.>进行API调用以获取新数据.>将从API检索到的数据写入后台线程中的数据库中.>从主线程上的数据库中读取数据并渲染UI.在步骤4中,数据应该是最新数据,但我们没有看到任何数据.解决方法具有runloops的线程上的Realm实例,例如主线程,updatetothelatestversionofthedataintheRealmfile,因为通知被发布到其线程的runloop.在后台

  5. ios – NSURLConnectionLoader线程中的奇怪崩溃

    我们开始看到我们的应用启动时发生的崩溃.我无法重现它,它只发生在少数用户身上.例外情况是:异常类型:EXC_BAD_ACCESS代码:KERN_INVALID_ADDRESS位于0x3250974659崩溃发生在名为com.apple.NSURLConnectionLoader的线程中在调用时–[NSBlockOperationmain]这是该线程的堆栈跟踪:非常感谢任何帮助,以了解可能导致这种崩

  6. ios – 合并子上下文时的NSObjectInaccessbileExceptions

    我尝试手动重现,但失败了.是否有其他可能发生这种情况的情况,是否有处理此类问题的提示?解决方法在创建子上下文时,您可以尝试使用以下行:

  7. ios – 从后台线程调用UIKit时发出警告

    你如何处理项目中的这个问题?

  8. ios – 在SpriteKit中,touchesBegan在与SKScene更新方法相同的线程中运行吗?

    在这里的Apple文档AdvancedSceneProcessing中,它描述了更新方法以及场景的呈现方式,但没有提到何时处理输入.目前尚不清楚它是否与渲染循环位于同一个线程中,或者它是否与它并发.如果我有一个对象,我从SKScene更新方法和touchesBegan方法(在这种情况下是SKSpriteNode)更新,我是否要担心同步对我的对象的两次访问?解决方法所以几天后没有回答我设置了一些实验

  9. ios – 在后台获取中加载UIWebView

    )那么,有一种方法可以在后台加载UIWebView吗?解决方法如果要从用户界面更新元素,则必须在应用程序的主队列(或线程)中访问它们.我建议您在后台继续获取所需的数据,但是当需要更新UIWebView时,请在主线程中进行.你可以这样做:或者您可以创建一个方法来更新UIWebView上的数据,并使用以下方法从后台线程调用它:这将确保您从正确的线程访问UIWebView.希望这可以帮助.

  10. ios – 何时使用Semaphore而不是Dispatch Group?

    我会假设我知道如何使用DispatchGroup,为了解问题,我尝试过:结果–预期–是:为了使用信号量,我实现了:并在viewDidLoad方法中调用它.结果是:从概念上讲,dispachGroup和Semaphore都有同样的目的.老实说,我不熟悉:什么时候使用信号量,尤其是在与dispachGroup合作时–可能–处理问题.我错过了什么部分?

随机推荐

  1. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  2. iOS – genstrings:无法连接到输出目录en.lproj

    使用我桌面上的项目文件夹,我启动终端输入:cd然后将我的项目文件夹拖到终端,它给了我路径.然后我将这行代码粘贴到终端中找.-name*.m|xargsgenstrings-oen.lproj我在终端中收到此错误消息:genstrings:无法连接到输出目录en.lproj它多次打印这行,然后说我的项目是一个目录的路径?没有.strings文件.对我做错了什么的想法?

  3. iOS 7 UIButtonBarItem图像没有色调

    如何确保按钮图标采用全局色调?解决方法只是想将其转换为根注释,以便为“回答”复选标记提供更好的上下文,并提供更好的格式.我能想出这个!

  4. ios – 在自定义相机层的AVFoundation中自动对焦和自动曝光

    为AVFoundation定制图层相机创建精确的自动对焦和曝光的最佳方法是什么?

  5. ios – Xcode找不到Alamofire,错误:没有这样的模块’Alamofire’

    我正在尝试按照github(https://github.com/Alamofire/Alamofire#cocoapods)指令将Alamofire包含在我的Swift项目中.我创建了一个新项目,导航到项目目录并运行此命令sudogeminstallcocoapods.然后我面临以下错误:搜索后我设法通过运行此命令安装cocoapodssudogeminstall-n/usr/local/bin

  6. ios – 在没有iPhone6s或更新的情况下测试ARKit

    我在决定下载Xcode9之前.我想玩新的框架–ARKit.我知道要用ARKit运行app我需要一个带有A9芯片或更新版本的设备.不幸的是我有一个较旧的.我的问题是已经下载了新Xcode的人.在我的情况下有可能运行ARKit应用程序吗?那个或其他任何模拟器?任何想法或我将不得不购买新设备?解决方法任何iOS11设备都可以使用ARKit,但是具有高质量AR体验的全球跟踪功能需要使用A9或更高版本处理器的设备.使用iOS11测试版更新您的设备是必要的.

  7. 将iOS应用移植到Android

    我们制作了一个具有2000个目标c类的退出大型iOS应用程序.我想知道有一个最佳实践指南将其移植到Android?此外,由于我们的应用程序大量使用UINavigation和UIView控制器,我想知道在Android上有类似的模型和实现.谢谢到目前为止,guenter解决方法老实说,我认为你正在计划的只是制作难以维护的糟糕代码.我意识到这听起来像很多工作,但从长远来看它会更容易,我只是将应用程序的概念“移植”到android并从头开始编写.

  8. ios – 在Swift中覆盖Objective C类方法

    我是Swift的初学者,我正在尝试在Swift项目中使用JSONModel.我想从JSONModel覆盖方法keyMapper,但我没有找到如何覆盖模型类中的Objective-C类方法.该方法的签名是:我怎样才能做到这一点?解决方法您可以像覆盖实例方法一样执行此操作,但使用class关键字除外:

  9. ios – 在WKWebView中获取链接URL

    我想在WKWebView中获取tapped链接的url.链接采用自定义格式,可触发应用中的某些操作.例如HTTP://我的网站/帮助#深层链接对讲.我这样使用KVO:这在第一次点击链接时效果很好.但是,如果我连续两次点击相同的链接,它将不报告链接点击.是否有解决方法来解决这个问题,以便我可以检测每个点击并获取链接?任何关于这个的指针都会很棒!解决方法像这样更改addobserver在observeValue函数中,您可以获得两个值

  10. ios – 在Swift的UIView中找到UILabel

    我正在尝试在我的UIViewControllers的超级视图中找到我的UILabels.这是我的代码:这是在Objective-C中推荐的方式,但是在Swift中我只得到UIViews和CALayer.我肯定在提供给这个方法的视图中有UILabel.我错过了什么?我的UIViewController中的调用:解决方法使用函数式编程概念可以更轻松地实现这一目标.

返回
顶部