From: Jonathan P. <jp...@dc...> - 2005-04-12 09:39:59
|
Hi, I've been trying to do some Ruby processing in the background (parsing a large XML file with REXML upon opening a document) while letting the Cocoa GUI still be responsive. I've tried a number of techniques to achieve this, and was wondering if anybody had any comments or suggestions - particularly to do with how Ruby threads are expected to work in RubyCocoa. I have a document window, and during loading I'm posting a sheet with an indeterminate progress bar in it. I want the progress bar to animate (and other windows/menus to be responsive) while doing the REXML parsing. (1) I've tried calling the run loop in a loop from a Ruby thread to get events processed whilst REXML works on the main Ruby thread. For some reason I haven't figured out, no events were processed. This may be because I was calling the run loop from within an existing run loop invocation. (2) I've tried doing the REXML processing in a Ruby thread and calling NSApplication endSheet. Both of these techniques suffered from the problem of invoking Cocoa methods from a non-main Ruby thread. Largely things would be okay, but occasionally AppKit would get unhappy because the per-NSThread exception handler list (NS_DURING etc) becomes invalid when Ruby switches the C stack between its threads. This is unfortunate, because it means we can't even call normally thread-safe Foundation methods because Cocoa exception handlers are used in RubyCocoa. Any ideas how to get around this? Thanks. Jonathan Below are some code fragments describing (1) and (2). (1) OSX::NSApplication.sharedApplication.beginSheet_...(@sheet,...) done = false rl = OSX::NSApplication.sharedApplication.currentRunLoop t = Thread.new do rl.runMode_beforeDate(OSX::NSDefaultRunLoopMode, OSX::NSDate.date) until done end @doc = REXML::Document.new(big_xml_string) ## code here to populate GUI from contents of @doc done = true t.join OSX::NSApplication.sharedApplication.endSheet(@sheet) (2) OSX::NSApplication.sharedApplication.beginSheet_...(@sheet,...) Thread.new do @doc = REXML::Document.new(big_xml_string) ## code here to populate GUI from contents of @doc OSX::NSApplication.sharedApplication.endSheet(@sheet) end |
From: kimura w. <ki...@us...> - 2005-04-13 17:20:57
|
Hi, The method NSApplication#beginSheet... is asynchronous. You not need to create a new thread. I wrote a testing app and it works fine. controller class: ---- class AppCtl < OSX::NSObject ib_outlet :window, :sheet def showSheet(sender) app = OSX::NSApplication.sharedApplication app.beginSheet(@sheet, :modalForWindow, @window, :modalDelegate, self, :didEndSelector, 'endSanmpleSheet', :contextInfo, nil) # wait... (1..3).each do |i| p i sleep 1 end app.endSheet(@sheet) end def endSanmpleSheet @sheet.orderOut(self) end end ---- NSApplication#endSheet does not close the sheet. We must call "orderOut" of the sheet in delegate method. http://developer.apple.com/documentation/Cocoa/Conceptual/Sheets/Tasks/UsingCustomSheets.html Tue, 12 Apr 2005 10:39:09 +0100, Jonathan Paisley wrote: >Below are some code fragments describing (1) and (2). > >(1) > > OSX::NSApplication.sharedApplication.beginSheet_...(@sheet,...) > > done = false > rl = OSX::NSApplication.sharedApplication.currentRunLoop > t = Thread.new do > rl.runMode_beforeDate(OSX::NSDefaultRunLoopMode, OSX::NSDate.date) >until done > end > > @doc = REXML::Document.new(big_xml_string) > ## code here to populate GUI from contents of @doc > > done = true > t.join > OSX::NSApplication.sharedApplication.endSheet(@sheet) > |
From: Jonathan P. <jp...@dc...> - 2005-04-13 17:47:44
|
On 13 Apr 2005, at 18:20, kimura wataru wrote: > The method NSApplication#beginSheet... is asynchronous. You not need > to create a new thread. I understand that beginSheet... is asynchronous, but the problem is that I want the Cocoa GUI to continue to respond to user interaction (other windows, menus, etc) whilst the ruby code that's doing the processing is working. Let's assume that the ruby method is called from the action of a button on a window. The method shows the sheet, does some lengthy processing, then hides the sheet. Therefore the call hierarchy is: run loop waiting for events handling mouse click event passing that to button button invokes action on its target <<ruby method here, doesn't return until lengthy processing complete>> The problem is that the lengthy processing (in your example the 1..3 sleep loop) doesn't allow for any GUI events to be processed. The run loop is suspended (handling our click event) until the ruby method returns. I don't want the GUI to lock up like this. What I wanted to be able to do was: def buttonHandler(sender) ...beginSheet(...) Thread.new do <<lengthy processing>> ...orderOut(...) ...endSheet(...) end end This fails (sometimes) because calling the Cocoa methods (orderOut, endSheet) from the non-main Ruby thread is unsafe. I'm sorry that I wasn't very clear in my first message. Does this explain the situation any better? > NSApplication#endSheet does not close the sheet. We must call > "orderOut" > of the sheet in delegate method. > > http://developer.apple.com/documentation/Cocoa/Conceptual/Sheets/ > Tasks/UsingCustomSheets.html You're right - I had forgotten about that in my example code. Many thanks for your help. Jonathan |
From: kimura w. <ki...@us...> - 2005-04-14 15:14:44
|
Thanks, I see that you said. RubyCocoa apps with ruby threads normarlly work fine, like SimpleApp.app in examples. But in this case, calling NSApplication#endSheet fails. I found NSApplication#endSheet succeeded when the method was invoked with new NSThread. I guess a sheet expects to receive "endSheet:" from other run-loop. app = OSX.NSApp app.beginSheet(@sheet, ...) Thread.start do <<some procedure>> # send NSApplication#endSheet to new NSThread OSX::NSThread.detachNewThreadSelector('endSheet:', :toTarget, app, :withObject, @sheet) end Wed, 13 Apr 2005 18:46:59 +0100, Jonathan Paisley wrote: >On 13 Apr 2005, at 18:20, kimura wataru wrote: > >The problem is that the lengthy processing (in your example the 1..3 >sleep loop) doesn't allow for any GUI events to be processed. The run >loop is suspended (handling our click event) until the ruby method >returns. I don't want the GUI to lock up like this. > >What I wanted to be able to do was: > >def buttonHandler(sender) > ...beginSheet(...) > Thread.new do > <<lengthy processing>> > ...orderOut(...) > ...endSheet(...) > end >end > >This fails (sometimes) because calling the Cocoa methods (orderOut, >endSheet) from the non-main Ruby thread is unsafe. > |
From: Jonathan P. <jp...@dc...> - 2005-04-14 16:20:09
|
On 14 Apr 2005, at 16:13, kimura wataru wrote: > RubyCocoa apps with ruby threads normarlly work fine, like > SimpleApp.app > in examples. But in this case, calling NSApplication#endSheet fails. > > I found NSApplication#endSheet succeeded when the method was invoked > with > new NSThread. I guess a sheet expects to receive "endSheet:" from other > run-loop. It's not the secondary run loop that's the problem, but rather unfortunate interactions between Ruby's thread scheduling and Cocoa's exception handling. Starting a new NSThread certainly solves the problem (since it uses a separate exception stack). I'll try to explain: Cocoa's exception handlers (NS_DURING etc) work by maintaining a per-pthread list of exception handlers. Therefore, when native code does: NS_DURING // some code NS_HANDLER // handler code NS_ENDHANDLER what actually happens is if (setjmp()) { // add jmp_buf to exception handler list for the current pthread // some code } else { // handler code } remove jmp_buf from exception handler list for the current thread The problem arises under the following circumstances (not related to endSheet at all): The main run loop calls a ruby callback, which spawns a thread. The secondary thread invokes an objective-c method which makes a callback to a ruby object. Ruby ruby callback runs long enough to yield back to the main thread. At this point, the NS_DURING exception handler for the objc method call (from ocm_perform) is on the exception handling stack, and if the main run loop handler returns (popping its exception handler) it'll find the wrong handler at the top of the stack. This causes a warning message, and then the program crashes. The following small program demonstrates this fairly repeatedly (doesn't happen every time though). The reason for this problem is that Ruby thread switching changes the stack, but Cocoa isn't aware of this. ############################################################# require 'osx/cocoa' class ExceptionTest < OSX::NSObject def foo Thread.new do self.performSelector_withObject(:bar,nil) end end def bar Thread.pass end end a = ExceptionTest.alloc.init t = a.performSelector_withObject(:foo,nil) t.join ############################################################# The error output looks like this: 2005-04-14 17:14:35.409 ruby[15510] *** Exception handlers were not properly removed. Some code has jumped or returned out of an NS_DURING...NS_HANDLER region without using the NS_VOIDRETURN or NS_VALUERETURN macros. /Library/Frameworks/RubyCocoa.framework/Versions/A/Resources/ruby/osx/ objc/oc_wrapper.rb:17: [BUG] Bus Error ruby 1.8.2 (2004-12-25) [powerpc-darwin7.7.0] |