Wednesday, October 31, 2012

Detecting UI Access from Threads

As most of you are aware, accessing user interface elements like controls and windows from a thread can cause crashes, deadlocks, and other bugs (especially with Cocoa on OS X). A good way to deal with this is to have a Timer update the UI based on information it retrieves from a thread (see Doing Progress Right).

However, there hasn't been an easy way to detect these problems in your applications without manually auditing the code or waiting until an error occurs. Until now. The command-line tool threadedui, works on OS X to check your applications to see if they access the user interface from a thread.


Download: threadedui.


To use threadedui, run it from Terminal supplying it with the path to your application. Like this:


# sudo ./threadedui /Applications/MyApp.app

This launches your application. Use it as you normally would. As it runs, information will appear in the Terminal window. Quit your application when you are finished.

If your application does not have any threaded UI access, then the output in Terminal looks something like this:

Launched /MyApp.app/Contents/MacOS/MyApp (24126)
DTrace attached to 24127
Main thread found, trace starting (may have missed some events)
Program exited, detected no UI operations from a thread

If your application does have threaded UI access, then significantly more information is displayed. This code has a Thread on a Window that directly updates a ProgressBar using this code in its Run event handler:

ProgressBar1.Value = 100

When run using threadedui, this is what the output looks like:

Launched /MyApp.app/Contents/MacOS/MyApp (24210)
DTrace attached to 24211
Main thread found, trace starting (may have missed some events)

   MyApp`REALbasic._UITrap
   MyApp`ProgressBar.Value.Set%%o<ProgressBar>i4i4+0x2c
   MyApp`Window1.Window1.Thread1_Run%%o<Window1.Window1>o<Thread>+0x97
   MyApp`Delegate.IM_Invoke%%o<Thread>+0x6e
   MyApp`AddHandler.Stub.0%%+0x33
   rbframework.dylib`0x001f48a0+0xc0
   CarbonCore`CooperativeThread+0x134
   libsystem_c.dylib`_pthread_start+0x14f
   libsystem_c.dylib`thread_start+0x22


Program exited, detected 1 UI operations from a thread


As you can see, the output tells you that the Thread1 Run event is accessing the UI and it specifically mentions ProgressBar.Value. Once the problem areas of you application have been identified in this manner, you can go about updating them.

10 comments:

Thomas Tempelmann said...

Great tool, very useful.

I've used dtrace for other complicated debugging tasks before. Takes some practise but can do astonishing things.

jaymcb74 said...

Excellent, thank you

Oliver Osswald said...

Wow ... found one ! And I was sure that I had already adjusted everything in the right way ...
Thanks a lot!

Beatrix Willius said...

Hmm.... In Cocoa accessing the UI from a thread simply causes a crash. So very obvious as not too miss. Will try out your tool....

amitava karan said...

We should not update UI from thread. Is this true for main thread also? or only for threads except the main thread?

Joe Ranieri said...

User interface elements should only be manipulated from the main thread. Usage from a non-main thread will result in undefined behavior.

jda said...

Sorry, where do I install threaded in OS X?

Jon

Joe Ranieri said...

It doesn't have to be installed anywhere -- you can just run it from wherever it was downloaded to.

jda said...

I've entered this in Terminal, and get a command not found error:

# sudo ./threadedui/MyAppFolder\ Development\ Folder/Myapp.app

Joe Ranieri said...

You're missing a space between ./threadedui and the application path.