Background: In order for ooRexx programmers getting acquainted
with BSF4ooRexx/Java quickly there are numerous nutshell
examples in "bsf4rexx/samples". Most of these nutshell examples
stem from observations of students over time who should get help
by demonstrating them how to achieve something of interest with
these (mostly brief) nutshell examples.
One interesting problem has been the interaction from ooRexx
with GUI objects which must be carried out on the GUI threads in
Java (the "awt thread" or the "JavaFX Application thread").
Although they got the necessary information about the
architecture and what to do in ordert to become able to send
messages on GUI threads, they kept running into problems, losing
a lot of time (even months because they could not get it working
in more complex programs).
To make a long story short, I came up with a message based
solution, that was very easy to understand and to employ for
them. None of the students ran into the GUI thread problems
since then.
The solution is an ooRexx class for awt (the Java "abstract
windows toolkit") named .AwtGuiThread and for JavaFX (a powerful
GUI system) .FxGuiThread, both subclassing a common superclass
.AbstractGuiThread. These classes allow one to send the ooRexx
message runLater[Latest](GUIreceiver, messageName, arguments)
which get queued and dispatched on the GUI thread later.
The nutshell examples
"bsf4rexx/samples/3-090_update_awtSwing_GUI-from-non-GUI-thread.rxj"
and
"bsf4rexx/samples/JavaFX/javafx_update_GUI-from-non-GUI-thread.rxj"
demonstrate how to employ this infrastructure. They have been
working for years without a problem.
While working on BSF4ooRexx I stumbled over an error (not having
run those two examples for quite some time) which seems to
indicate that ooRexx now creates an error when being used from
different threads:
F:\work\svn\bsf4oorexx\trunk\bsf4oorexx\samples>3-090_update_awtSwing_GUI-from-non-GUI-thread.rxj
screenSize: [java.awt.Dimension[width=1920,height=1080]]
winSize : [java.awt.Dimension[width=800,height=200]]
xPos=[560] yPos=[440]
a REXXEVENTHANDLER::actionPerformed - starting Rexx thread
The SOME_REXX_CLASS class::updateGuiFromRexxThread - just arrived, GUI
thread: 23808
The SOME_REXX_CLASS class::updateGuiFromRexxThread - now running on
thread: 7428
*-* Compiled method "DELETE" with scope "Queue".
5727 *-* msgQueue~delete(idx) -- delete the guiMsg object
5637 *-* forward message "REMOVEMESSAGE" continue -- remove all GUI
messages of the same name targeted to the same object
207 *-* .AwtGuiThread~runLaterLatest(label, "setText", "i", str)
Error 93 running
F:\work\svn\bsf4oorexx\trunk\bsf4oorexx\samples\3-090_update_awtSwing_GUI-from-non-GUI-thread.rxj
line 207: Incorrect
call to method.
Error 93.966: Incorrect queue index "1".
a REXXEVENTHANDLER::windowClosing - release lock ('closeApp=.true')
which will allow a blocked 'waitForExit' method to resume and return
F:\work\svn\bsf4oorexx\trunk\bsf4oorexx\samples>
and
F:\work\svn\bsf4oorexx\trunk\bsf4oorexx\samples\JavaFX>javafx_update_GUI-from-non-GUI-thread.rxj
a REXXBUTTONHANDLER::handle - starting Rexx thread
The SOME_REXX_CLASS class::updateGuiFromRexxThread - just arrived, GUI
thread: 24244
The SOME_REXX_CLASS class::updateGuiFromRexxThread - now running on
thread: 14124
*-* Compiled method "DELETE" with scope "Queue".
5727 *-* msgQueue~delete(idx) -- delete the guiMsg object
5637 *-* forward message "REMOVEMESSAGE" continue -- remove all GUI
messages of the same name targeted to the same object
194 *-* .FxGuiThread~runLaterLatest(label, "setText", "i", str)
Error 93 running
F:\work\svn\bsf4oorexx\trunk\bsf4oorexx\samples\JavaFX\javafx_update_GUI-from-non-GUI-thread.rxj
line 194: Incorrect call to method.
Error 93.966: Incorrect queue index "1".
The ooRexx code where the error occurs looks like, lines # 5637
and # 5727 are highlighted in green and bold:
/*
-------------------------------------------------------------------------------------------------
*/
-- method replaces existing target: this way only the latest sent
message will get executed!
/** This class method allows to define a Rexx message with its
arguments that should
* be processed on the GUI thread. Each invocation will create a new
<code>GUIMessage</code>
* from the supplied arguments and returns it for further inspection
in addition to queueing
* it for later execution on the GUI thread. Unlike
<code>runLater</code> this method will
* first remove any queued messages with the same target and the same
message name, before
* queueing this message.
*
* @param target the target object to receive a message on the GUI
thread
* @param messageName the message name to send to the target on the
GUI thread
* @param indicator optional; indicates with "I"
(Indivdual) that the arguments
* are listed individually, "A" (Array)
indicates that the fourth
* argument is an array containing the arguments to
supply with the
* message on the GUI thread
*
* @return GUIMessage a GUI message object (modelled after ooRexx'
<code>Message</code> class
* that allows one to inspect the state of the
message (process completed,
* fetching a possible result, determining whether
an error occurred and
* inspecting it)
*/
::method runLaterLatest class
use strict arg target, messageName, ...
signal on syntax
if self~JavaGuiUtilityClz=.nil then self~setup -- make sure
attributes are initialized
*forward message "REMOVEMESSAGE" continue -- remove all GUI
messages of the same name targeted to the same object*
guiMsg=result -- fetch returned GUIMessage object
msgQueue=self~msgQueue -- get message queue
msgQueue~queue(guiMsg) -- now enqueue current (latest) guiMsg
if self~waitingToRun=.false then -- "runLater" not invoked yet?
do
self~waitingToRun=.true
self~invokeOnGuiThread -- make sure Java will dispatch Rexx
message on GUI thread
end
return guiMsg -- allows caller to get a hold of a
possible return value
syntax:
raise propagate
... cut ...
/*
-------------------------------------------------------------------------------------------------
*/
-- method removes all GUI message objects
/** This private class method removes all GUIMessage objects from the
message queue that will be processed
* later when Java call backs on the GUI thread, that have the
supplied target and the supplied
* message name.
*
* @param target the target object to receive a message on the GUI
thread
* @param messageName the message name to send to the target on the
GUI thread
* @param indicator optional; indicates with "I"
(Indivdual) that the arguments
* are listed individually, "A" (Array)
indicates that the fourth
* argument is an array containing the arguments to
supply with the
* message on the GUI thread
*
* @return GUIMessage a GUI message object (modelled after ooRexx'
<code>Message</code> class
* that allows one to inspect the state of the
message (process completed,
* fetching a possible result, determining whether
an error occurred and
* inspecting it)
*/
::method removeMessage class private
use strict arg target, messageName, ...
signal on syntax
if self~JavaGuiUtilityClz=.nil then self~setup -- make sure
attributes are initialized
-- remove all occurrences of messages with the same messagename
and the same target
msgQueue=self~msgQueue -- get message queue
idx=msgQueue~first -- get index of the first item in the
queue, if any
do while idx<>.nil -- a valid index in hand ?
tmpItem=msgQueue~at(idx) -- fetch item, a GuiMessage
-- same target, same messagename?
if tmpItem~target=target,
tmpItem~messageName~caselessEquals(messagename) then
*msgQueue~delete(idx) -- delete the guiMsg object***
idx=msgQueue~next(idx) -- get index of next item in the queue
end
guiMsg=self~createGuiMessage(arg(1,"array")) -- create and return
GUI message object
return guiMsg -- allows caller to get a hold of a
possible return value
syntax:
raise propagate
None of the methods of these three classes are marked as
unguarded such that in the past if one method executes no other
can do so in parallel, inhibiting concurrent access of the queue.
However, I recall that there was a change in ooRexx 5.0 that if
methods do not have an expose keyword statement than unlike
earlier versions of ooRexx such expose-less methods will be made
unguarded.
Yet, as in this real use case this change may bring
incompatibilities for existing programs (BSF4ooRexx has been
around for more than 10 years). The problem lies in getting and
using the queue in different threads because of this (at the
time of devising the solution unforeseen) as no default blocking
of methods takes place.
To double-check I added manually "guarded" to all methods of the
classes that play together here and doing so, made the nutshell
programs work again.
---
The reason I bring this forward is twofold:
* the change may break existing programs and programmers may
be clueless as to why that happens and therefore not know
how to solve it,
* it should be discussed whether keeping this change, but if
so it needs to be prominently communicated as the cause may
be too subtle for many, especially if using ooRexx code for
which they do not have the source:
o one solution may be to have a new OPTION
unguared|guarded determining how expose-less methods
should be treated; this way this default to unguarded
behavior needs to be activated explicitly making the
programmer aware of it, yet older programs still would
function with the default to guarded behavior that was
in place in erarlier versions of ooRexx,
o another solution would be to not only analyze whether
the expose statement exists, but in the case where it
does not exist also analyze whether messages to self
occur and if so leave the method guarded; however this
bears the problem that one could use an object that
refers to self such that the problem might surface
nevertheless.
Any thoughts, ideas, suggestions, conclusions?
---rony