Random Freeze when threading UI.

I have some long processing and want to update the user on what is happening.

So I have created a Scrolling TextView in Interface Builder (in fact it is a NSScroll view) and I’m writing to it to inform of the progress.

so in my script I have a property that is linked to the NSScrollview in IB

	property myScrollView : missing value

And while doing my processing I send sting to my textView using the handler below that is called from my processing routines.


	on logToMyScrollView(aMessage)
		myScrollView's documentView()'s textStorage()'s mutableString()'s appendString_(aMessage & return)
		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 code works fine and it scroll to the end as I wish, except of course that the update on the textView happens once the processing is done, which deserves my goal.

The issue I have is that when I thread my processing handler to let the UI updates to happen I have freeze of the application at various time, sometime even right when I press my “go” button, sometimes when the processing it’s almost done, however I never get the all things to be done except when I don’t thread bench().

Here is how I threaded my application


	on runTest_(sender)
		performSelectorInBackground_withObject_("bench", MyAppDelegate)
	end runTest_

And my bench() handler is doing the job and calling logToMyScrollView() handler.


on bench()
 logToMyScrollView("doing this")
 --doing some work
 logToMyScrollView("I done with this")
 logToMyScrollView("doing that")
 --doing some work
 logToMyScrollView("I done with that")
-- and so on

end bench

If I sample the application with activity monitor while it is frozen, the main thread looks to be locked on __semwait_signal (see below)
This is also the symbol display on debugger when I force quit the application.

So I’m wondering if I’m not missing some handlers that are called by the NSScrollView that I’m not serving hence the freeze.

Anybody can help ?

Call graph:
2719 Thread_76975 DispatchQueue_1: com.apple.main-thread (serial)
2719 start
2719 main
2719 NSApplicationMain
2719 -[NSApplication run]
2719 -[NSApplication sendEvent:]
2719 -[NSWindow sendEvent:]
2719 -[NSControl mouseDown:]
2719 -[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:]
2719 -[NSCell trackMouse:inRect:ofView:untilMouseUp:]
2719 -[NSControl sendAction:to:]
2719 -[NSApplication sendAction:to:from:]
2719 _CF_forwarding_prep_0
2719 forwarding
2719 -[BAObjectProto forwardInvocation:]
2719 -[BAObjectProto invokeScriptHandler:args:error:]
2719 +[BAObjectProto invokeScriptHandler:forObject:args:error:]
2719 OSAExecuteEvent
2719 AppleScriptComponent
2719 CallComponentFunction
2719 ASExecuteEvent(AEDesc const*, unsigned int, int, unsigned int*)
2719 UASExecute1()
2719 UASRemoteSend(unsigned char, unsigned char, unsigned char, unsigned char, unsigned char, unsigned char*)
2719 TUASApplication::Send(TStackFrame_UASRemoteSend*, AEDesc*, AEDesc*, unsigned char, unsigned char, unsigned char)
2719 ComponentSend(AEDesc const*, AEDesc*, int, int)
2719 AppleScriptUnlocker::~AppleScriptUnlocker()
2719 _pthread_cond_wait
2719 __semwait_signal

So I tried to update UI on main thread using performSelectorOnMainThread_withObject_waitUntilDone_ for writing text to my textview.

I put also autorelease stuff in my main

#import <Cocoa/Cocoa.h>
#import <AppleScriptObjC/AppleScriptObjC.h>

int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

[[NSBundle mainBundle] loadAppleScriptObjectiveCScripts];

[pool release];

return NSApplicationMain(argc, (const char **) argv);

}

here below is simple code that reproduce the issue.
If I stress a bit the application by pressing repetitively the run button I got the same freeze on __sem_wait_signal in despite I’m updating UI on the main thread


--
--  ASparallelAppDelegate.applescript
--  ASparallel
--
--  Created by r42555 on 9/22/10.
--  Copyright 2010 Motorola Mobility. All rights reserved.
--

script ASparallelAppDelegate
	property parent : class "NSObject"
	property theCount : 0
	property aMessage : ""
	property myScrollView : missing value
	
	on applicationWillFinishLaunching_(aNotification)
		-- Insert code here to initialize your application before any files are opened 
	end applicationWillFinishLaunching_
	
	on applicationShouldTerminate_(sender)
		-- Insert code here to do any housekeeping before your application quits 
		return current application's NSTerminateNow
	end applicationShouldTerminate_
	
	on applicationShouldTerminateAfterLastWindowClosed_(sender)
		return current application's NSTerminateNow
	end applicationShouldTerminateAfterLastWindowClosed_
	
	on buttonPush_(sender)
		set theCount to theCount + 1
		set aMessage to "ButonPush_"
		performSelectorOnMainThread_withObject_waitUntilDone_("logToMyScrollView", missing value, true)
		performSelectorInBackground_withObject_("myTest", ASparallelAppDelegate)
	end buttonPush_
	
	on myTest()
		set myCount to theCount
		repeat 100 times
			log "Running test " & myCount & " "
			set aMessage to "Running test " & myCount & " "
			performSelectorOnMainThread_withObject_waitUntilDone_("logToMyScrollView", missing value, true)
			--delay 1
		end repeat
		log "ending test " & myCount & " "
		set aMessage to "ending test " & myCount & " "
		performSelectorOnMainThread_withObject_waitUntilDone_("logToMyScrollView", missing value, true)
	end myTest
	
	on logToMyScrollView()
		myScrollView's documentView()'s textStorage()'s mutableString()'s appendString_(aMessage & return)
		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
	
end script

I’m not sure what the problem is, but I don’t think it’s ever going to do what you want. The problem is that although AppleScript can theoretically run multi-threaded, your application only has one instance of AppleScript so only one thread can run at a time. If you launch several threads, as new ones are launched, the old ones are put on hold until the new ones are finished.

To Shane, yes I know that AS will run only one thread at a time and it is not multithreaded per say.
The thread you are passing the hand to suspending the other.

However if the second thread write a string on an UI object on the main thread it works well on a Text Item or a textView, at least when using performSelectorOnMainThread

And second sample code I provide show it well. I’m no sure why it end up blocking under stress.
I assume because It creates a lot of thread blocking each others by nature of AS that is not multi-threaded.

I make my code working.

I had two issues.

  • 1st is that I forgot to comment or I uncomment along the road the direct call to handler I called earlier in the code in background.
    So it causes some messes .
  • 2nd, I was calling the method to write to the scroll view text item in the thread within a tell to finder statement. And it looks to be a source of trouble.

Once remove those two issues and use


performSelectorOnMainThread_withObject_waitUntilDone_("logToMyScrollView", missing value, true)

to execute the write of the string on the main thread my application works and my scrollview is updated while I’m doing my processing in the second thread.

I have tried to use false for the waitUntilDone and it works as well but with some delay.
I have not tried without using performSelectorOnMainThread after having made my two fixes as it is a bit of code change.

So I assume that the performSelectorOnMainThread give back the hand to the main thread.

One thing I did not succeed to do is do pass an argument, e.g. the sting to append to textView.
I always got an error saying that argument {} was not matching what logToMyScrollView(aMessage) was waiting for.

So I end-up using a property that I set before calling logToMyScrollView().

This is not ideal.

If somebody know how to pass my string to my selector with performSelectorOnMainThread, I would like to do it.

I’m glad you got it sorted out!

Your problem is the name of your handler; if it takes an argument, it needs to end in an underscore to match Cocoa’s colon:

   on logToMyScrollView_(aMessage)

Then call it with:

performSelectorOnMainThread_withObject_waitUntilDone_("logToMyScrollView:", :Some text", true)