on mainHandler_(sender)
-- do some stuff
my performSelectorInBackground_withObject_("BkgndProcess", missing value)
-- .
end mainHandler_
on BkgndProcess()
-- several actions, actually preformed
repeat -- the requested times
set calulatedValue to my subHandler(var1, var2, var3) -- FAILS to call the routine
-- following code : not executed
end repeat
-- finishing statements : not executed
end BkgndProcess
on subHandler(param1, param2, param3)
-- here processing for tempResult
return tempResult
end subHandler
Such script will run perfectly if not using routines in the background,
but written as above the handler sent in the bkgnd is unable to call its child handler.
Where am I wrong?
When performing a selector in the background it will lose scope. So the best way is sending itself as the argument to the selector and make calls from that argument object. Pretty much like IBAcions
on mainHandler_(sender)
-- do some stuff
my performSelectorInBackground_withObject_("BkgndProcess", me)
-- .
end mainHandler_
on BkgndProcess_(sender)
-- several actions, actually preformed
repeat -- the requested times
set calulatedValue to sender's subHandler(var1, var2, var3) -- FAILS to call the routine
-- following code : not executed
end repeat
-- finishing statements : not executed
end BkgndProcess
on subHandler(param1, param2, param3)
-- here processing for tempResult
return tempResult
end subHandler
Unfortunately I failed to make it works. The code stops at the same place when the loop is calling for the first time the /subhandler/.
For those who are interested, this the context of the problem:
It’s an app which ran fine since Lion up to Yosemite.
Because of a possibly time consuming loop I put a floating “progress-window”, in front of the main window, showing a progress bar and a counter. Cancelling the process was available by pressing Cmd+Ctrl keys.
All done in the unique thread.
Now with El Capitan the same app still achieves well its job but the “progress-window” doesn’t appear until the loops are over. Anyway, canceling-keys still work and at this moment the window is shown with the bar and counter stopped at the state of the progression.
And another info : tested on the same machine, the original app (same version and code) was fast with Mavericks, almost 50% slower with Yosemite and regained its speed (even lightly faster than with Mavericks) when running under El Capitan. Unfortunately, there is this issue with user interface.
So I tried to use a threaded process, sending in the background the heavy work and calling UI display on a front thread. But yet I’m stuck with handlers which don’t perform in background.
That’s what you would do in, say, Objective-C, but it doesn’t work in AppleScript. The problem is that your application has one instance of AppleScript, and it can only do one thing at a time – so when you try to run two threads, one is paused until the other is finished.
Have a look at chapter 13 of the 5th edition of my book. That’s about the best you can do, I think.
OK, unless I get rid of the process bar I have to try to apply the workaround you explain in your book.
Too bad, I succeeded to avoid it until El Capitan
Thanks!
As Shane mentioned there is one single instance of AppleScript so you can perform a selector in the background but AppleScript will block itself. Too bad the whole debugging tools of OpenScripting are removed from the cocoa/coreservices framework because you could create another AppleScript component then and then run it in there (also break-point could be supported again in Xcode), the only bottleneck would be the single instance of the event manager.
Since you mentioned you wanted to create a progress view, you could also use NSTask to run the executed code in another process using osascript (separate script). The process has probably nothing to do with your application itself and your app is nothing more than monitoring how far it exactly is right? What you need to do is looping on the main thread and reading output from your script using an NSTimer.
At least that is how I run my scripts on the background when using AppleScriptObjC. For feedback from the backrgound script into the application i use a fifo file and read it using NSFileHandle. This way I’m sure that all data from the script is only read once, you could use a file and only read the last data. The information in the file is uncompiled AppleScript code which can easily be compiled using the run script command and used right way.
If I’m understanding you correctly, you can create another AS component now using OSAKit. But last time I played with it, it still exhibited similar problems.
If you pm me your mail address I can send you a clean small xcode project with what I meant.
I was maybe too brief. The component can still be created but with the ‘old’ framework you could call and run certain handlers from specific script objects inside components. Then you would be able to fork an component and it would feel like it’s running in the background. Or am I missing something and is it still possible?
I’m not sure we’re on the same track or not. This shows the approach I’m talking about:
use scripting additions
use framework "Foundation"
use framework "OSAKit"
on doStuff()
-- create a new AppleScript instance
set anOSALanguageInstance to current application's OSALanguageInstance's languageInstanceWithLanguage:(current application's OSALanguage's defaultLanguage())
-- make script and run it from the new instance
set theSource to "use framework \"Foundation\"
repeat with i from 1 to 20
delay 0.1
current application's NSLog(\"On second instance, second thread %d\", i) -- writes to Console
end repeat"
set theScript to current application's OSAScript's alloc()'s initWithSource:theSource fromURL:(missing value) languageInstance:anOSALanguageInstance usingStorageOptions:(current application's OSANull)
set {theResult, theError} to theScript's compileAndReturnError:(reference)
theScript's executeAndReturnError:(reference)
end doStuff
on doItInTheBackground()
-- call the above handler on a separate background thread
my performSelectorInBackground:"doStuff" withObject:(missing value)
current application's NSLog("doStuff has been called") -- writes to Console
end doItInTheBackground
-- set the above to run after 1 second
my performSelector:"doItInTheBackground" withObject:(missing value) afterDelay:1
-- start a repeat loop
repeat with i from 1 to 20
delay 0.1
current application's NSLog("On default instance, first thread %d", i) -- writes to Console
end repeat
Even though the second loop is run on a separate instance on a background thread, the foreground script stops while it runs.
I understand what you’re saying now. The outgoing call, with the immediate incoming call to the same AppleScript component that is only loaded once by AppleScriptObjC framework will not allow you run threads in the background. The lock forces AppleScript to wait until the current stack call is finished in line. Threads will behave like a normal LIFO system and scheduled tasks will behave FIFO. So it’s more an AppleScriptObjC issue rather than an AppleScript issue right? In one single instance of AppleScript, multiple components can be ran at the same time in a preemptive manner, it’s AppleScriptObjC that doesn’t allow it.
As you can see NSThread en PerformSelector will not block the threads and will run simultanous and not in a LIFO or FIFO queue. The log times between start and stop is a continuously 5 seconds and will not be bothered by other threads in the order they started. So in some sort of way it works preemptive. However for scheduled tasks, it will do a full block in FIFO mode as you have described because they ran on the default thread.
Well, your discussion reaches height that I’m not ready to follow (I doubt I could ever).
Anyway my app works fine with or without the progress window, that’s only count.
For my modest problem, as soon I get time I’ll try the simplest way possible to make that dispensable feature of a progress bar to work.
I certainly won’t hit my head against walls to make it happens. I script for fun now I’m a retired man
Kinds regards to you.
PS: in relation with this app, I just finished to write a vanilla Applescript calculating Moon eclipses (after Jean Meeus algorithms).
If someone is interested in, I can post it in a MacScripter forum (Which one?). Let me know.
use scripting additions
use framework "Foundation"
use framework "OSAKit"
-- create a new AppleScript instance
set anOSALanguageInstance to current application's OSALanguageInstance's languageInstanceWithLanguage:(current application's OSALanguage's defaultLanguage())
-- make script and run it from the new instance
set theSource to "use framework \"Foundation\"
repeat with i from 1 to 20
delay 0.001
current application's NSLog(\"On second instance, second thread %d\", i) -- writes to Console
end repeat"
set theScript to current application's OSAScript's alloc()'s initWithSource:theSource fromURL:(missing value) languageInstance:anOSALanguageInstance usingStorageOptions:(current application's OSANull)
set {theResult, theError} to theScript's compileAndReturnError:(reference)
-- set the above running
theScript's performSelectorInBackground:"executeAndReturnError:" withObject:(missing value)
-- start a repeat loop
repeat with i from 1 to 20
delay 1.0E-3
current application's NSLog("On default instance, on first thread %d", i) -- writes to Console
end repeat