Random freeze of App when calling performSelectorOnMainThread

I have a quiet long program so I want to display some progress information about what is happening.

So I start the long treatment using


performSelectorInBackground_withObject_("bench", missing value)

Almost when entering in ‘bench’ I post message to the main thread for logging.


		set userMSG to (current date) as text
		if writeTestCheck's objectValue() then
			set userMSG to userMSG & " : Write Test: "
		else
			set userMSG to userMSG & " : Read test: "
		end if
		log "in bench() will call performSelectorOnMainThread"
		
		performSelectorOnMainThread_withObject_waitUntilDone_("logToMyScrollView:", userMSG, true)

logToMyScrollView is wrting the text in input at the end of my scroll view.


	on logToMyScrollView_(aMessage)
		myScrollView's documentView()'s textStorage()'s mutableString()'s appendString_((aMessage as text) & return)
		--scroll to the end of range
		set newTextLength to myScrollView's documentView()'s textStorage()'s mutableString()'s |length|() as integer
		myScrollView's documentView()'s scrollRangeToVisible_({|location|:newTextLength, |length|:0})
	end logToMyScrollView_

The freeze is random, however it always happen the 1st time I call performSelectorOnMainThread_withObject_waitUntilDone_(“logToMyScrollView:”, userMSG, true) in the background thread.
If this call works, all the subsequent calls works fine.

I know from the log I put that when freezing the program reach this point and I can see that my log message are not happening.

If I sample the process here is what I’m getting.

As any one an idea of what I’m doing wrong there ?

Sampling process 2417 for 3 seconds with 1 millisecond of run time between samples
Sampling completed, processing symbols…
Analysis of sampling MassStorageBench (pid 2417) every 1 millisecond
Process: MassStorageBench [2417]
Path: /Users/r42555/Documents/DevProjects/MassStorageBench/build/Release/MassStorageBench.app/Contents/MacOS/MassStorageBench
Load Address: 0x100000000
Identifier: com.yourcompany.MassStorageBench
Version: 1.0 (1.0)
Code Type: X86-64 (Native)
Parent Process: launchd [213]

Date/Time: 2011-02-01 11:05:22.061 +0100
OS Version: Mac OS X 10.6.6 (10J567)
Report Version: 6

Call graph:
2480 Thread_36512 DispatchQueue_1: com.apple.main-thread (serial)
2480 start
2480 NSApplicationMain
2480 -[NSApplication run]
2480 -[NSApplication sendEvent:]
2480 -[NSWindow sendEvent:]
2480 -[NSControl mouseDown:]
2480 -[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:]
2480 -[NSCell trackMouse:inRect:ofView:untilMouseUp:]
2480 -[NSControl sendAction:to:]
2480 -[NSApplication sendAction:to:from:]
2480 _CF_forwarding_prep_0
2480 forwarding
2480 -[BAObjectProto forwardInvocation:]
2480 -[BAObjectProto invokeScriptHandler:args:error:]
2480 +[BAObjectProto invokeScriptHandler:forObject:args:error:]
2480 OSAExecuteEvent
2480 AppleScriptComponent
2480 CallComponentFunction
2480 ASExecuteEvent(AEDesc const*, unsigned int, int, unsigned int*)
2480 UASExecute1()
2480 UASActor_Send(unsigned char, unsigned char, unsigned char)
2480 UASRemoteSend(unsigned char, unsigned char, unsigned char, unsigned char, unsigned char, unsigned char*)
2480 TUASApplication::Send(TStackFrame_UASRemoteSend*, AEDesc*, AEDesc*, unsigned char, unsigned char, unsigned char)
2480 ComponentSend(AEDesc const*, AEDesc*, int, int)
2480 AppleScriptUnlocker::~AppleScriptUnlocker()
2480 _pthread_cond_wait
2480 __semwait_signal
2480 Thread_36529 DispatchQueue_2: com.apple.libdispatch-manager (serial)
2480 start_wqthread
2480 _pthread_wqthread
2480 _dispatch_worker_thread2
2480 _dispatch_queue_invoke
2480 _dispatch_mgr_invoke
2480 kevent
2480 Thread_36552
2480 thread_start
2480 _pthread_start
2480 PrivateMPEntryPoint
2480 TSystemNotificationTask::SystemNotificationTaskProc(void*)
2480 CFRunLoopRun
2480 CFRunLoopRunSpecific
2480 __CFRunLoopRun
2480 mach_msg
2480 mach_msg_trap
2480 Thread_36560: com.apple.CFSocket.private
2480 thread_start
2480 _pthread_start
2480 __CFSocketManager
2480 select$DARWIN_EXTSN
2480 Thread_36561
2480 thread_start
2480 _pthread_start
2480 NSThread__main
2480 -[NSUIHeartBeat _heartBeatThread:]
2479 usleep
2469 nanosleep
2469 __semwait_signal
10 cerror
9 cerror
1 cthread_set_errno_self
1 -[NSApplication _keyWindowForHeartBeat]
1 objc_msgSend
2480 Thread_36578
2480 thread_start
2480 _pthread_start
2480 NSThread__main
2480 CF_forwarding_prep_0
2480 forwarding
2480 -[BAObjectProto forwardInvocation:]
2480 -[BAObjectProto invokeScriptHandler:args:error:]
2480 +[BAObjectProto invokeScriptHandler:forObject:args:error:]
2480 OSAExecuteEvent
2480 AppleScriptComponent
2480 CallComponentFunction
2480 ASExecuteEvent(AEDesc const*, unsigned int, int, unsigned int*)
2480 UASExecute1()
2480 UASActor_Send(unsigned char, unsigned char, unsigned char)
2480 UASRemoteSend(unsigned char, unsigned char, unsigned char, unsigned char, unsigned char, unsigned char*)
2480 TUASApplication::Send(TStackFrame_UASRemoteSend*, AEDesc*, AEDesc*, unsigned char, unsigned char, unsigned char)
2480 ComponentSend(AEDesc const*, AEDesc*, int, int)
2480 BASendProc
2480 InvokeHandler
2480 -[NSInvocation invoke]
2480 invoking

2480 -[NSObject(NSThreadPerformAdditions) performSelectorOnMainThread:withObject:waitUntilDone:]
2480 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:]
2480 -[NSCondition wait]
2480 _pthread_cond_wait
2480 __semwait_signal
1151 Thread_38445
1151 start_wqthread
1151 _pthread_wqthread
1151 __workq_kernreturn
1151 Thread_38446
1151 start_wqthread
1151 _pthread_wqthread
1151 __workq_kernreturn
1 Thread_38445 DispatchQueue_34: TFSVolumeInfo::GetSizingGCDQueue (serial)
1 start_wqthread
1 _pthread_wqthread
1 _dispatch_worker_thread2
1 _dispatch_queue_invoke
1 _dispatch_queue_drain
1 _dispatch_queue_invoke
1 _dispatch_source_invoke
1 __dispatch_source_mig_create_block_invoke_1
1 dispatch_mig_server
1 dispatch_rcv_msg
1 FSEventsD2F_server
1 _Xcallback_rpc
1 implementation_callback_rpc
1 malloc
1 malloc_zone_malloc
1 szone_malloc_should_clear
1 tiny_malloc_from_free_list

Total number in stack (recursive counted multiple, when >=5):

Sort by top of stack, same collapsed (when >= 5):
__semwait_signal 7429
kevent 2480
mach_msg_trap 2480
select$DARWIN_EXTSN 2480
__workq_kernreturn 2302
cerror 9

The short story is that trying to perform stuff in the background is pointless in ASObjC (unless what you are calling is actual Objective-C). because it all has to be handled by a single instance of AppleScript. The only way one AS “thread” can run is by blocking others.

Also, if I may suggest an improvement, I would change this line:

myScrollView's documentView()'s scrollRangeToVisible_({|location|:newTextLength, |length|:0})

by creating an you NSRange before. It helped me solve some odd behavior. Also, coercing some data helps, like the newTextLength variable. My suggestion:

set rangeToScrollTo to {|location|:((newTextLength) as string), |length|:0}
myScrollView's documentView()'s scrollRangeToVisible_(rangeToScrollTo)

I applied the same principle a lot to my code and somehow it makes it more stable. I do not think though that this would help your problem with threading, because as Shane said, it’s an impossible task. You can give the appearance of a light multi-threading app, but never will it be a true multi-thread.

Unless we misunderstood your question? To be honest, you dumped a lot of data on us, it’s difficult to get your point… sorry! :slight_smile:

Browser: Safari 531.22.7
Operating System: Mac OS X (10.6)

I’m perfectly fine with the limitation of multithreading in applescript for that application.

I really just want to display some messages while my tests are in progress, so a light multi-threading is enough for my case.

So If I make logToMyScrollView_ handler an objective C handler instead of an applescript one one, should I be fine ?

Not if it’s interacting with the UI – that can’t be done on a background thread whatever the language.

I can only guess what you’re trying to do, but if you’re running a loop where you want the screen updated each time through, try having the loop handler call itself using performSelector:withObject:after:. That will ensure a new event loop, and hence screen update.

I do not want to update the UI on background thread.

I have the main thread which is the ApplescriptAppDelegate.
The UI is controlled by this thread.
In the main window I have added a NSScrollview.

When I start my long treatment, I want to write string onto this NSScrollView.

As Applescript thread is blocking and so I cannot refresh UI, I run the long treatment in background using


performSelectorInBackground_withObject_("bench", missing value)

At this time bench is running on a new thread in background.

On this thread I have made an handler logToMyScrollView, really just to avoid duplicating code to log message on to the NSScrollView, that I ask to be executed on the main thread like this.


		performSelectorOnMainThread_withObject_waitUntilDone_("logToMyScrollView:", userMSG, true)

this handler is just calling Cocoa methods.


	to logToMyScrollView_(aMessage)
		log "entering logToMyScrollView" & " " & (aMessage as text)
		myScrollView's documentView()'s textStorage()'s mutableString()'s appendString_((aMessage as text) & return)
		--scroll to the end of range
		set newTextLength to myScrollView's documentView()'s textStorage()'s mutableString()'s |length|() as integer
		myScrollView's documentView()'s scrollRangeToVisible_({|location|:newTextLength, |length|:0})
	end logToMyScrollView_

My understanding is that this AS handler will block the AS caller, but the call to the cocoa method are executed onto the main thread.
And it looks to happen that way as my UI is refreshing.
And it is not an issue that the AS caller gets blocked for a while.

Today all of this works quite well, except sometimes the first call to

performSelectorOnMainThread_withObject_waitUntilDone_("logToMyScrollView:", userMSG, true)

causes a freeze, I never reach the logToMyScrollView handler.

So far once the 1st call is successful, all the subsequent calls works, and I do about 40-50 calls to

performSelectorOnMainThread_withObject_waitUntilDone_("logToMyScrollView:", userMSG, true)

For me it looks more as a race condition the first time things are put int place.

Good luck. All my attempts to do something similar had the same problem, with random failure at the first call. I gave up and started writing code that gives the event loop regular chances to run, using either performSelector:withObject:after: or NSEvent’s nextEventMatchingMask:untilDate:inMode:dequeue: and sendEvent:. Both seem to work reliably.

I think I understand something here: sgasp’s definition of a thread seems to be somewhat distorted. In AppleScriptObjC (or plain old Applescript), whether you have one or 300 script files (or classes) in your app, they will all be executed on the main thread, thus they are only single-threaded. Only more advanced languages like Objective-C can be running in a multi-threaded environment. A thread only exists when the app is actually running. Thus using methods like performSelectorInBackground_withObject_ or performSelectorOnMainThread_withObject_waitUntilDone_, even though they are accessible, will only be usefull in the Objective-C world, since ASOC is single-threaded.

So when you say “I have the main thread.”, I think you mean the main delegate. Now I see what you are trying to do with your app, and it is possible to update the UI when a task is done, but using methods like you are using now will certainly not help. Using methods like Shane suggested in his last post do work. If you modify your code with his suggestions, I think you will have no problem updating the UI.

Is my idea making sense or am I off by a mile? :slight_smile:

Browser: Safari 531.22.7
Operating System: Mac OS X (10.6)

Yes I mean the main delegate and I guess I have to either use the applescript bridge in a pure objective C app or do what suggest Shane.

Nothing straight forward.

I think I have a good grasp of what you want to do, and I have done something similar in the past and it is entirely possible to do it. In fact, instead of calling a performSelector:afterDelay kind of method, why don’t you call it after whatever operation is done, if it’s inside a repeat loop or whatever? performSelector kind of methods are better used to delay actions you would like to do after other operations outside of the normal sequence of operations. I think what you want to do can be done inside a normal sequence, not need for delayed actions. Of course, I don’t have your whole app in front of me to be totally certain of what i’m saying, I’ll let you be the judge of that.