Tim F. wrote:
It ran fine for me on XP (thanks!) but for some reason when running it
on my Mac it just freezes up as soon as it starts… I’m on Leopard
(10.5.2) using MacPorts for ruby with the latest wxruby gem… Any ideas
what might be happening??
I tried it on 10.5.2 with system ruby + 1.9.4 gem - after a few runs, I
see what you mean. I got this error message once.
2008-02-28 23:06:02.105 ruby[64018:10b] *** 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.
Which makes me think that the thread scheduler is jumping out of some
critical portion of GUI code. I reworked the example so that all GUI
updating is done through the main thread, using monitor to check
progress with thread-local variables. This seems to work reliably now on
OS X.
hth
alex
__
def start_job(idx)
Thread.abort_on_exception = true
url = “blah #{idx}”
fname = File.basename(url)
@dialog.status_label.label = “Faking download #{idx} of #{10}:
#{fname}”
@dialog.update
@dialog.progress_all.value = (100 * (idx/10.0)).to_i
@worker = Thread.new do
Thread.current[:progress] = 0
1.upto(10) do | count |
break if @dialog.cancelled
Thread.current[:progress] = (100 * (count/10.0)).to_i
sleep(0.1)
end
if @dialog.cancelled
Thread.stop
end
end
end
Test the state of the queue and worker progress
def monitor_jobs
if not @worker # Get started
@dialog.show
start_job(@job_n)
elsif @worker and @job_n < 10 # Still running
if @dialog.cancelled # test for cancellation
finish
end
if @worker.status
@dialog.progress_item.value = @worker[:progress]
else # start next
start_job(@job_n += 1)
end
else # completed
finish
end
end
Hi!
I followed the ‘problem with Gauge on windows xp…’ discussion and I’m
having difficulties to understand the resulting code from Alex. As my
problems are not connected to Tims problem, I start a new one.
Please bear in mind that these are questions because I would like to
understand the code and are not meant as a critical comment about the
code.
Am 28 Feb 2008 um 23:31 hat Alex F. geschrieben:
def start_job(idx)
Thread.abort_on_exception = true
url = “blah #{idx}”
fname = File.basename(url)
@dialog.status_label.label = “Faking download #{idx} of #{10}: #{fname}”
- Is there any advantage of writing “#{10}” instead of “10” here, I
don’t see?
@dialog.update
@dialog.progress_all.value = (100 * (idx/10.0)).to_i
@worker = Thread.new do
Thread.current[:progress] = 0
- Is Thread.current not identical with self inside the block?
- Why do we have to define the thread-local progress here? Without
this line it would also be defined as a thread-local variable in the
upto-block, wouldn’t be a block variable and would so survive the block
cycles, wouldn’t it?
1.upto(10) do | count |
break if @dialog.cancelled
Thread.current[:progress] = (100 * (count/10.0)).to_i
- Why is the former @dialog.update now unnecessary?
sleep(0.1)
end
if @dialog.cancelled
Thread.stop
end
- Why not directely ‘Thread.stop if @dialog.cancelled’ in the upto-
block? finish exits in this case anyway. What am I overlooking?
end
end
Am 28 Feb 2008 um 21:15 hat Alex F. geschrieben:
@dialog.progress_all.value = 100
@dialog.hide
md = Wx::MessageDialog.new(@dialog, 'Fake download(s) complete!',
TITLE, Wx::OK, Wx::DEFAULT_POSITION)
md.show_modal
@dialog.hide
- Why do we need ‘@dialog.hide’ two times?
code = 0
end
@dialog.destroy
exit(code)
end
Thanks in advance if someone is willing to help me understand.
Dirk
Alex,
thank you for your clear and helpful explanations.
Dirk
Hi Dirk
Dirk T. wrote:
I followed the ‘problem with Gauge on windows xp…’ discussion and I’m
having difficulties to understand the resulting code from Alex. As my
problems are not connected to Tims problem, I start a new one.
Cool - it’s been a useful thread for me too.
Please bear in mind that these are questions because I would like to
understand the code and are not meant as a critical comment about the
code.
I made quick minimal changes to the original code to address the
threading only, and Tim’s code was extracted from a real app, so some
elements may well be redundant.
@dialog.status_label.label = “Faking download #{idx} of #{10}: #{fname}”
- Is there any advantage of writing “#{10}” instead of “10” here, I
don’t see?
No, there isn’t. I guess in Tim’s code it was a place holder where in
the real code a variable would go.
@dialog.update
@dialog.progress_all.value = (100 * (idx/10.0)).to_i
@worker = Thread.new do
Thread.current[:progress] = 0
- Is Thread.current not identical with self inside the block?
No, self remains the same as it was (in this case, the instance of
XRCTest). It’s a new execution thread, but nothing about the scope
changes. Compare - a File.open do … end block doesn’t change “self” to
the file within it.
- Why do we have to define the thread-local progress here? Without
this line it would also be defined as a thread-local variable in the
upto-block, wouldn’t be a block variable and would so survive the block
cycles, wouldn’t it?
Because potentially “monitor” could be called again in the main thread,
and in that, access the local variable before the execution in the
worker thread reaches the point where the local variable is set. Then
it’d be passing nil to progress.value= which is an exception.
Try putting a “sleep 1” in the line immediately before this, and see
what happens.
1.upto(10) do | count |
break if @dialog.cancelled
Thread.current[:progress] = (100 * (count/10.0)).to_i
- Why is the former @dialog.update now unnecessary?
I think Tim was calling update because he wasn’t seeing the dialog
refreshing. But it happened not to be the root of the problem. All of
the in-built GUI widgets in wxRuby redraw themselves automatically as
their state changes.
Calling fit or layout upon a sizer is only needed when the contents of a
container are changed - a widget is removed, or added.
sleep(0.1)
end
if @dialog.cancelled
Thread.stop
end
- Why not directely ‘Thread.stop if @dialog.cancelled’ in the upto-
block? finish exits in this case anyway. What am I overlooking?
That should work too and save a few lines, though I haven’t tested.
@dialog.progress_all.value = 100
@dialog.hide
md = Wx::MessageDialog.new(@dialog, 'Fake download(s) complete!',
TITLE, Wx::OK, Wx::DEFAULT_POSITION)
md.show_modal
@dialog.hide
- Why do we need ‘@dialog.hide’ two times?
We don’t
hth
alex
Alex F. wrote:
- Is there any advantage of writing “#{10}” instead of “10” here, I
don’t see?
No, there isn’t. I guess in Tim’s code it was a place holder where in
the real code a variable would go.
That is indeed the case…
- Why is the former @dialog.update now unnecessary?
I think Tim was calling update because he wasn’t seeing the dialog
refreshing. But it happened not to be the root of the problem. All of
the in-built GUI widgets in wxRuby redraw themselves automatically as
their state changes.
Calling fit or layout upon a sizer is only needed when the contents of a
container are changed - a widget is removed, or added.
I was trying anything that seemed relevant to get the dialog to resize
as I changed the label text Actually I still find the call to #fit
necessary in order to have the dialog resize when the label text grows
beyond what the current dialog size can show…
- Why not directely ‘Thread.stop if @dialog.cancelled’ in the upto-
block? finish exits in this case anyway. What am I overlooking?
That should work too and save a few lines, though I haven’t tested.
I condensed to that as I factored this code back into my app with no ill
effects…
Thanks again to Alex for all the help with this! One good thing that has
come out of this - it has definitely reinforced my commitment to
cross-platform testing as I develop…
Cheers,
Tim
Tim F. wrote:
@worker.join(0.095) # << this is what I added
…
As I understand it, the limit parameter gives the Thread a certain
amount of time to work (in this case 95ms) before resuming the normal
scheduling behavior … the end result in this case seems to be a better
balance between ui responsiveness and thread activity for this
particular app.
Interesting, I’ve never used this parameter ebefore.
My question: since this is my first use of Thread with wx, does anyone
see any potential pitfalls I may have overlooked?
None that I can see. Overall I think experience with Threads + wxRuby so
it’s mainly a case of trying to see what works across platforms. Thanks
for keeping us posted on your findings.
Would there be any harm in wrapping the Thread.join call with some kind
of flag to tell monitor_queue to skip over its normal processing if the
worker thread is currently joined?
Not that I can see. I’m not sure it would ever reach that point - would
the main thread start running again until the limit expires?
cheers
alex
Alex F. wrote:
Interesting, I’ve never used this parameter ebefore.
Me either It does seem to work, though, joining for the amount of
time specified… This gave me a little more flexibility to time things
in such a way that the ui generally stays responsive while still letting
the download thread have some undivided time to work…
Downloads are much faster for me this way… they do still feel a
little bit throttled but overall it feels like a better balance so I am
happy for now… although I may try adjusting the timer/limit to smaller
increments and see how that affects ui responsiveness and download
speeds…
Would there be any harm in wrapping the Thread.join call with some kind
of flag to tell monitor_queue to skip over its normal processing if the
worker thread is currently joined?
Not that I can see. I’m not sure it would ever reach that point - would
the main thread start running again until the limit expires?
Well, maybe I’m off the mark here but I was thinking that the Timer
would fire the monitor-queue method call every 100ms regardless of the
status of the joined thread and that, as such, it might end up calling
the method while a previous invocation was still active… I may be
overthinking it though! I have not yet had much exposure to Ruby’s
threads…
Cheers,
Tim
Tim F. wrote:
Downloads are much faster for me this way… they do still feel a
little bit throttled but overall it feels like a better balance so I am
happy for now… although I may try adjusting the timer/limit to smaller
increments and see how that affects ui responsiveness and download
speeds…
Well, if it helps anyone else, I discovered that setting the join limit
too low caused crashes, at least on the Mac… I ended up using 10ms
monitor interval and a 50ms join limit… I also had to remove the check
of @dialog.canceled from within the thread as this occasionally led to
malloc double free errors. Now the monitor_queue method just kills the
thread if the user requests to cancel…
Well, maybe I’m off the mark here but I was thinking that the Timer
would fire the monitor-queue method call every 100ms regardless of the
status of the joined thread and that, as such, it might end up calling
the method while a previous invocation was still active…
This concern proved to be unfounded as joining the thread inhibits the
timer from firing until the thread releases the join…
Thanks to everyone for all the help with this!!
Cheers,
Tim
Well, all is working fine now with a few tweaks… so consider this a
followup with a small question
I ended up with a situation where the worker thread was not getting
enough time (i.e. downloads were very slow) with the initial approach of
using a timer to call Thread.pass every 10ms and, I noticed a slight
improvement as I lowered that down (eventually) to every 1ms… but this
still downloaded considerably slower than the “thread-less” console
version I started from.
In an attempt to address this, I did away with the timer calling
Thread.pass and elected instead to add a Thread#join(limit) call just
after the progress bar status is updated, like so:
# Test the state of the queue and worker progress
protected
def monitor_queue
if not @worker # start queue
@dialog.show
process_queue_item(@current_item)
elsif @worker and @current_item < @queue.size - 1
finish(@worker.status, @worker[:tmpfile]) if @dialog.cancelled
# still running?
if @worker.status # update progress bar
@dialog.progress_item.value = @worker[:progress]
@worker.join(0.095) # << this is what I added
else # start next
process_queue_item(@current_item += 1)
end
else
finish(@worker.status, @worker[:tmpfile])
end
end
As I understand it, the limit parameter gives the Thread a certain
amount of time to work (in this case 95ms) before resuming the normal
scheduling behavior … the end result in this case seems to be a better
balance between ui responsiveness and thread activity for this
particular app.
My question: since this is my first use of Thread with wx, does anyone
see any potential pitfalls I may have overlooked?
I did notice in testing that the Wx::Timer resolution seemed to vary
significantly from platform to platform (as the docs suggested would be
the case) so I suppose that there could be a bit of contention between
the Thread.join call and successive calls to the monitor_queue method…
Would there be any harm in wrapping the Thread.join call with some kind
of flag to tell monitor_queue to skip over its normal processing if the
worker thread is currently joined?
# Test the state of the queue and worker progress
protected
def monitor_queue
if not @working
if not @worker # start queue
@dialog.show
process_queue_item(@current_item)
elsif @worker and @current_item < @queue.size - 1
finish(@worker.status, @worker[:tmpfile]) if @dialog.cancelled
# still running?
if @worker.status # update progress bar
@dialog.progress_item.value = @worker[:progress]
@working = true
@worker.join(0.095)
@working = false
else # start next
process_queue_item(@current_item += 1)
end
else
finish(@worker.status, @worker[:tmpfile])
end
end
end
Thanks for any feedback…
Cheers,
Tim