2009/6/6 Svend Haugaard Sørensen [email protected]
# changing a progress bar, label, or whatever.
function. That only run once and the gets removed from the event que.
http://rubyforge.org/mailman/listinfo/wxruby-users
Another solution to this, instead of using evt_idle, is two fold, and it
bit
more trickier to setup, but get’s the job done, of keeping the app
looking
like it is active. The first thing, is to continue with asking your
dialog,
once your dialog is answered / dismissed / whatever, you proceed with
your
basic logic of what the next step should be. For this case, I’ll give
you
an example of asking a user for a file to process, and how to proceed,
in
logical human terms, instead of actual code.
First would be to prompt the user with a file dialog, with the run of
show_modal on a new instance of Wx::FileDialog. The return value of
show_modal() should be either Wx::ID_OK, or Wx::ID_CANCEL. If it’s
Wx::ID_CANCEL, then simply return from the method that you created to
handle
the event. And all goes on as normal. But if it’s Wx::ID_OK, then we
move
to the next step of gathering the information provided by the Dialog.
So we get the name of the file, and store it into an instance variable
on
our object that we are currently working from, weither it be window, or
whatever you call the event from. Once we have saved the filename, we
don’t
process anything, or start anything at the moment. At this point, we
need
to decide on which design we can go with, and this depends on what kind
of
processing we are doing.
We can do the first process, which is an Un-interrupted process, in
which we
create a new task, or in this case, Thread, where our tight knitted
program
logic will run in, and you just type it out as one big loop that will
actually process the file in question. The thing you need to note here,
that with this kind of design, you need to put somewhere in the loop (I
usually do it at the end of the loop iteration), a Thread.pass, which
switches between the internal Ruby Threads. I will explain the reason
for
this in a moment. After you completed your loop, and end the new Thread
you
created in the writting of it, you add a new Wx::Timer event. The
Wx::Timer
event will have one purpose, and that’s to allow internal Ruby context
switch between threads, again using the Thread.pass method.
However, if your processing can be done in intervals, and isn’t intricle
that you need to process everything immediately, then you can do what I
call, timer based execution. With Timer based execution, you do
something
similar to the Ruby Threads above, but instead of creating a thread and
a
timer, you only create a Wx::Timer, and use some instance variables to
keep
track of what your doing. And what you do, is you set the timer for a
pre-determined interval, at which it will execute, and during the
execution
of the Timer event, you do your actual processing of the file. So we
move
the processing of the file from the Thread, into the Timer Event
handler.
Now, with the two processes explained, as far as the programming design
of
it, I’ll go into detail about Ruby Threads, and usage of Timers with
this.
It should also be noted, that with this setup, is optimal for Ruby 1.8
series. The reason for this, and the reason for the Timer in the first
place for this method, is that in Ruby 1.8 series, Threads are green
threads. Meaning that they are software layer threads. They are
entirely
managed by the Ruby interpreter itself, and never goes out to the
Operating
System itself to control the thread. This is where you have quite a few
problems with Wx::Ruby, and Ruby itself. Now the reason why it’s
limited to
the Ruby 1.8 Series, and earlier actually, is cause the Ruby 1.9 series,
to
be dubbed 2.0, uses Operating System Threads. And as Alex has explained
to
me on this subject, You can create threads in Ruby 1.9, and Wx::Ruby,
and
things will work, as long as you keep any updates that you do the GUI in
the
Main Thread. So if you need to update a progress bar, or display some
text
in a window, it’s best to do that in the main thread, and not the
sub-thread
that you create for the processing of the file.
To further enlighten as to why Green Threads can be problematic, is
there’s
truely only 2 threads ever created in the entire running of your
program.
The Thread that Ruby operates on, and the thread that wxWidgets runs in.
Even though a Ruby Thread in 1.8, is still a thread of sorts, it’s
severly
limited when there are 2 Operating System threads running in a single
Ruby
Program. So basically what happens, when you see a “Non-Responsive”
window
in wxRuby, when you know that your program is processing stuff, comes
from
the fact that Ruby’s OS Thread, has taken priority for processing, and
simply ignores the wxRuby OS Thread, till it has completed all of it’s
work. Now remember, this only applies to Ruby 1.8, as Ruby 1.9 uses OS
threads fully in the Thread class implementation.
So, in our above usage of Ruby’s Thread, and Wx::Timer, is a sort of way
to
say hey, Ruby, give wxWidgets some time to process stuff. And we give
what
we call, time share, between the threads, that has to be distinctively
written into the code, and passed around between the two OS Thread.
What we
are doing, is that in our Ruby thread, we’re saying, okay run this loop,
and
do some processing of the stuff, when it’s completed it’s work in this
iteration, we’re going to go ahead, and tell the Ruby Interpreter, allow
other threads to run, which in turn, gives back control to wxRuby to do
it’s
event processing. When there is no other events to execute, or
specifically
when our timer ticks, it passes control back to the Ruby interpreter,
and
says alright, you can allow another thread to process some information.
So in a way, we have to do negotiation between the two threads, in order
to
make it work properly, and keep the application from looking like it’s
dead-lock. You could even use the evt_idle method, to put the
Thread.pass
in, as evt_idle will be called when wxWidgets has nothing else to do,
but
you still need to put the Thread.pass into your thread, once your done
with
a iteration loop of your processing code. If you don’t, the thread will
continue on, till it’s finished processing, and you’ll still be stuck
with
the looking non-responsive application window. You have to keep the
balance
between the Ruby 1.8 threads, and the wxWidgets OS thread, so that both
can
continue to execute.
I hope that this enlightens some of you now, and future programmers as
to
why there are certain things that need to be done, in order to make Ruby
1.8
Green Threads cooperative with Operating System Threads, as this would
be
needed for any C/C++ Library, that utilizies a library that uses
Operating
System Threads, when your stuck with Green Threads in Ruby 1.8.
hth,
Mario