Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

threads and exception in wxPython

7 views
Skip to first unread message

Zunbeltz Izaola

unread,
Nov 2, 2004, 11:28:16 AM11/2/04
to

Hi,

I've an wxPython windows that creates a thread. An object of this
thread raised an exception and i want to cach it in the main thread,
that of the windows (I want this becouse the first implementation
doesn't use thread). I know that exception can not be pass between
thread.

I've read something about wraping the run() method of the threaded
object in a try/except block and save the exception for inspection ,
but i don't know how to implement it. Can you give me and advice?

TIA

Zunbeltz

--
Zunbeltz Izaola Azkona | wmbizazz at lg dot ehu
dotes
Materia Kondentsatuaren Fisika Saila |
Zientzia eta Teknologia Fakultatea | Phone: 34946015326
Euskal Herriko Unibertsitatea |
PK 644 | Fax: 34 944648500
48080 Bilbo (SPAIN) |

Jaime Wyant

unread,
Nov 2, 2004, 12:15:43 PM11/2/04
to pytho...@python.org
I needed to do the same thing and ended up defining a custom event to
post back to the mainframe. You can get instructions on implementing
custom events in wxPython at the wxWiki here ->

http://wiki.wxpython.org/index.cgi/CustomEventClasses

If you need more help, just bark.

jw

On 02 Nov 2004 17:28:16 +0100, Zunbeltz Izaola

> --
> http://mail.python.org/mailman/listinfo/python-list
>

Josiah Carlson

unread,
Nov 2, 2004, 12:18:34 PM11/2/04
to Zunbeltz Izaola, pytho...@python.org

Zunbeltz Izaola <zunb...@wm.lc.ehu.es.XXX> wrote:
> I've an wxPython windows that creates a thread. An object of this
> thread raised an exception and i want to cach it in the main thread,
> that of the windows (I want this becouse the first implementation
> doesn't use thread). I know that exception can not be pass between
> thread.
>
> I've read something about wraping the run() method of the threaded
> object in a try/except block and save the exception for inspection ,
> but i don't know how to implement it. Can you give me and advice?

from StringIO import StringIO
import traceback
import Queue

traceback_queue = Queue.Queue()

...
def run(self):
try:
...
except:
tb = StringIO()
traceback.print_exc(tb)
traceback_queue.put(tb.getvalue())
...


- Josiah

Zunbeltz Izaola

unread,
Nov 3, 2004, 3:41:39 AM11/3/04
to
Josiah Carlson <jcar...@uci.edu> writes:

> from StringIO import StringIO
> import traceback
> import Queue
>
> traceback_queue = Queue.Queue()
>
> ...
> def run(self):
> try:
> ...
> except:
> tb = StringIO()
> traceback.print_exc(tb)
> traceback_queue.put(tb.getvalue())
> ...
>
>
> - Josiah
>

Ok, thanks for the help. But I still have a problem. When the lines
in the try block raise an exception it is saved in the traceback_quee
and the thread is finished. Is that correct?. The problem is how do I
inspect the queue? The function that creates the thread is something
like that

def OnMeasurement(self):
self.CurrentMeasurement.start()

If I put some code after the .start() method the queue will be
empty. Do I need to insert a infinite loop to see when the exception
is raised?

TIA

Zunbeltz Izaola

Antoon Pardon

unread,
Nov 3, 2004, 8:25:30 AM11/3/04
to
Op 2004-11-02, Zunbeltz Izaola schreef <zunb...@wm.lc.ehu.es.XXX>:

>
> Hi,
>
> I've an wxPython windows that creates a thread. An object of this
> thread raised an exception and i want to cach it in the main thread,
> that of the windows (I want this becouse the first implementation
> doesn't use thread). I know that exception can not be pass between
> thread.

Yes they can. Unfortunately in order to protect us from ourselves
the powers that be, decided to only allow this to people who know C
and have access to a C compilor.

from http://docs.python.org/api/threads.html:

int PyThreadState_SetAsyncExc( long id, PyObject *exc)
Asynchronously raise an exception in a thread. The id argument is
the thread id of the target thread; exc is the exception object to
be raised. This function does not steal any references to exc. To
prevent naive misuse, you must write your own C extension to call
this. Must be called with the GIL held. Returns the number of thread
states modified; if it returns a number greater than one, you're in
trouble, and you should call it again with exc set to NULL to revert
the effect. This raises no exceptions. New in version 2.3.


I find this most unfortunate, because in my view being naive is
not in opposition with easily knowing to write a C extention.
I also find it a bad idea to force people to know C for a feature
which has some perfect uses for people who don't know C and normally
have no need to know C.

--
Antoon Pardon

Peter Hansen

unread,
Nov 3, 2004, 9:18:14 AM11/3/04
to
Antoon Pardon wrote:
> Op 2004-11-02, Zunbeltz Izaola schreef <zunb...@wm.lc.ehu.es.XXX>:
> Yes they can. Unfortunately in order to protect us from ourselves
> the powers that be, decided to only allow this to people who know C
> and have access to a C compilor.
>
> int PyThreadState_SetAsyncExc( long id, PyObject *exc)

There was a comment by someone that perhaps ctypes would allow
calling this directly. I don't know whether that's possible,
but if it is it's probably a fairly simple operation. I've
used ctypes in a fairly limited fashion, so I suspect it
would take me a while to figure it out, but this much works
anyway:

>>> import ctypes
>>> py = ctypes.dll.python23
<CDLL 'python23', handle 1e000000 at 9dfb98>
>>> py.PyThreadState_SetAsyncExc
<ctypes._CdeclFuncPtr object at 0x0098FB88>

Presumably the "id" above is a non-issue, but I think you
need to do something more complex to get a PyObject reference
to an exception in ctypes... not yet in my repertoire.

A quick test gave this when trying the brainless approach,
with t being a thread and exc a KeyboardInterrupt instance.

>>> py.PyThreadState_SetAsyncExc(id(t), byref(exc))
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: expected CData instance


Anyone? Anyone? Bueller?

-Peter

Peter Hansen

unread,
Nov 3, 2004, 10:17:35 AM11/3/04
to
Peter Hansen wrote:
> >>> py.PyThreadState_SetAsyncExc(id(t), byref(exc))
> Traceback (most recent call last):
> File "<stdin>", line 1, in ?
> TypeError: expected CData instance

First error in the above: the thread id is not id(thread)
but the value returned by thread.start_new_thread or the
value returned by threading._get_ident().

Also ctypes provides a py_object class which I have no
idea how to use but could guess at wildly.

Also, ctypes provides a "pythonapi" thingy which should
be used in preference to cdll.python23, so the code
becomes more like this:

>>> asyncexc = ctypes.pythonapi.PyThreadState_SetAsyncExc
>>> exc = ctypes.py_object(ValueError)
>>> asyncexc(t.id, exc)
1
>>> t
<T(Thread-2, started)>

Hmm... clearly that's not enough, but perhaps closer.

(I find it hard to believe nobody has tried this yet, unless
it's the case that those who know how to do it also know that
it's actually not possible for some reason...)

-Peter

Peter Hansen

unread,
Nov 3, 2004, 10:20:28 AM11/3/04
to
Peter Hansen wrote:
> >>> asyncexc = ctypes.pythonapi.PyThreadState_SetAsyncExc
> >>> exc = ctypes.py_object(ValueError)
> >>> asyncexc(t.id, exc)
> 1
> >>> t
> <T(Thread-2, started)>
>
> Hmm... clearly that's not enough, but perhaps closer.

But shortly after, trying to create a new thread, I got:

>>> t3 = T()


Traceback (most recent call last):
File "<stdin>", line 1, in ?

File "<stdin>", line 2, in __init__
File "c:\a\python23\lib\threading.py", line 384, in __init__
self.__block = Condition(Lock())
File "c:\a\python23\lib\threading.py", line 148, in Condition
return _Condition(*args, **kwargs)
File "c:\a\python23\lib\threading.py", line 154, in __init__
if lock is None:
ValueError
>>> t3 = T()
>>> t3
<T(Thread-4, started)>
>>> t3.id
5740
>>> t
<T(Thread-2, started)>


So the exception managed to get to the new thread, even though
I called SetAsyncExc *before* the thread was even created.

Now _that's_ what I call "asynchronous". ;-)

-Peter

Peter Hansen

unread,
Nov 3, 2004, 10:35:02 AM11/3/04
to
Peter Hansen wrote:
> So the exception managed to get to the new thread, even though
> I called SetAsyncExc *before* the thread was even created.
>
> Now _that's_ what I call "asynchronous". ;-)

Actually, it's what you call pilot error. I was retrieving
threading._get_ident() in the thread initializer *before*
the thread was even started... stupid pilot.

Here's the final working version:

>>> class T(threading.Thread):
... def __init__(self):
... threading.Thread.__init__(self)
... self.stopped = False
... self.start()
... def run(self):
... self.id = threading._get_ident()
... try:
... while not self.stopped:
... time.sleep(0.1)
... except:
... print 'thread terminated!'
...
>>> t = T()
>>> t
<T(Thread-7, started)>


>>> asyncexc = ctypes.pythonapi.PyThreadState_SetAsyncExc
>>> exc = ctypes.py_object(ValueError)
>>> asyncexc(t.id, exc)
1

>>> thread terminated!

>>> t
<T(Thread-7, stopped)>


-Peter

Richie Hindle

unread,
Nov 3, 2004, 10:51:27 AM11/3/04
to pytho...@python.org

[Peter]

>>> asyncexc = ctypes.pythonapi.PyThreadState_SetAsyncExc
>>> exc = ctypes.py_object(ValueError)
>>> asyncexc(t.id, exc)

We'll *never* get ctypes into the standard library now. 8-)

--
Richie Hindle
ric...@entrian.com

Thomas Heller

unread,
Nov 3, 2004, 12:50:05 PM11/3/04
to
Peter Hansen <pe...@engcorp.com> writes:

^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> >>> asyncexc(t.id, exc)

I'm not sure this is the way to do it (but py_object is completely
undocumented, so far, and I don't rememeber the details myself). But,
it is fine to pass the id() of a python object, which is an integer
specifying it's address, when the function expects a PyObject*:

asyncexc(t.id, id(ValueError(42)))

> 1
> >>> thread terminated!
>
> >>> t
> <T(Thread-7, stopped)>
>
>
> -Peter

Thomas

Nick Craig-Wood

unread,
Nov 3, 2004, 1:30:30 PM11/3/04
to
Peter Hansen <pe...@engcorp.com> wrote:
> >>> t = T()
> >>> t
> <T(Thread-7, started)>
> >>> asyncexc = ctypes.pythonapi.PyThreadState_SetAsyncExc
> >>> exc = ctypes.py_object(ValueError)
> >>> asyncexc(t.id, exc)
> 1
> >>> thread terminated!
>
> >>> t
> <T(Thread-7, stopped)>

On a related subject, I'm trying to forcibly terminate a thread thats
already running. I don't see any means of doing this in the docs.
Sending it an exception would be ideal. Is the above the only way of
doing this?

--
Nick Craig-Wood <ni...@craig-wood.com> -- http://www.craig-wood.com/nick

Peter Hansen

unread,
Nov 3, 2004, 1:50:20 PM11/3/04
to
Nick Craig-Wood wrote:
> Peter Hansen <pe...@engcorp.com> wrote:
>
>> >>> t = T()
>> >>> t
>><T(Thread-7, started)>
>> >>> asyncexc = ctypes.pythonapi.PyThreadState_SetAsyncExc
>> >>> exc = ctypes.py_object(ValueError)
>> >>> asyncexc(t.id, exc)
>> 1
>> >>> thread terminated!
>>
>> >>> t
>><T(Thread-7, stopped)>
>
>
> On a related subject, I'm trying to forcibly terminate a thread thats
> already running. I don't see any means of doing this in the docs.
> Sending it an exception would be ideal. Is the above the only way of
> doing this?

This isn't just a related subject, this is exactly what
I was trying to do above.

I have a test case here which is failing, however, because
the exception is *not* being delivered asynchronously for
some reason, but appears in the thread only when the thread
is already terminating itself. I'm still experimenting, but
I will probably post the failing test case shortly for
others to play with.

Nick, if you want to learn more, read the threads from this
google search: http://groups.google.ca/groups?q=PyThreadState_SetAsyncExc

Then you'll know as much as anyone about this...

-Peter

Michael Hobbs

unread,
Nov 3, 2004, 2:35:11 PM11/3/04
to
Zunbeltz Izaola <zunb...@wm.lc.ehu.es.xxx> wrote:
> Ok, thanks for the help. But I still have a problem. When the lines
> in the try block raise an exception it is saved in the traceback_quee
> and the thread is finished. Is that correct?. The problem is how do I
> inspect the queue? The function that creates the thread is something
> like that
>
> def OnMeasurement(self):
> self.CurrentMeasurement.start()
>
> If I put some code after the .start() method the queue will be
> empty. Do I need to insert a infinite loop to see when the exception
> is raised?

In addition to adding the exception to the queue, you should post
a custom event to the main window or wx.App. The callback bound to
that event would then read the exception out of the queue and print
it, raise it, send an email, whatever.

-- Mike

Thomas Heller

unread,
Nov 3, 2004, 3:02:09 PM11/3/04
to pytho...@python.org, pe...@engcorp.com
Peter Hansen <pe...@engcorp.com> writes:

Can it be that the exception is delivered asynchronously, but still has
to be checked by some code somewhere (my guess would be ceval.c)?
It seems sys.setcheckinterval() plays a role here.

I'l append my script at the end, I have the impression that the thread
has to execute quite some byte codes before it notices the pending
exception. That's the reason for the 'for i in range(100):' loop in the
thread.

Thomas

<code>
import thread, time, sys
from ctypes import pythonapi, py_object

##sys.setcheckinterval(1)

def run():
while 1:
time.sleep(0.3)
for i in range(100):
pass
sys.stdout.write(".")

exc = ValueError(42)

t = thread.start_new_thread(run, ())
# allow the thread to start
time.sleep(1)

# raise exception in thread
print pythonapi.PyThreadState_SetAsyncExc(t, id(exc))


##print pythonapi.PyThreadState_SetAsyncExc(t, py_object(exc))

# allow thread to print the exception
time.sleep(1)
print "done"

</code>

Peter Hansen

unread,
Nov 3, 2004, 3:24:37 PM11/3/04
to
Thomas Heller wrote:

> Peter Hansen <pe...@engcorp.com> writes:
>>I have a test case here which is failing, however, because
>>the exception is *not* being delivered asynchronously for
>>some reason, but appears in the thread only when the thread
>>is already terminating itself.
>
> Can it be that the exception is delivered asynchronously, but still has
> to be checked by some code somewhere (my guess would be ceval.c)?
> It seems sys.setcheckinterval() plays a role here.

Exactly! That is definitely it. I was sitting in a small
loop doing a time.sleep() every so often, and during the
entire test that thread probably executed only about forty
or fifty bytecodes.

Your technique with the "busy loop" works, as does setting
sys.setcheckinterval() to a much smaller value than the
default 100.

Thanks Thomas!

-Peter

Peter Hansen

unread,
Nov 3, 2004, 3:46:31 PM11/3/04
to
Thomas Heller wrote:
> # raise exception in thread
> print pythonapi.PyThreadState_SetAsyncExc(t, id(exc))

I should note for the record that neither Thomas'
example above nor even my more complex one
(posted earlier, but unfortunately messed up on
my news server so I can't reply to it), implements
the required API fully.

The docs for that function say that you should check
the return value, and if it returns a value greater
than 1, you must call it again with a NULL as the
second argument (anyone know if None would be
okay when using ctypes?), or you probably risk screwing
up even more than this messy way of killing a
thread will screw things up normally.

-Peter

Thomas Heller

unread,
Nov 3, 2004, 3:45:13 PM11/3/04
to
Peter Hansen <pe...@engcorp.com> writes:

Correct.

And yes, None is the ctypes way to spell NULL (although it would accept
the interger 0 as well).

Thomas

Josiah Carlson

unread,
Nov 3, 2004, 5:30:29 PM11/3/04
to Zunbeltz Izaola, pytho...@python.org

In your main thread (the one handling the GUI), you can set up a
wx.Timer() whose bound event checks the queue every second or so.

As another poster mentioned, you can also post events to the GUI, which
can either say "there is an exception traceback in the queue" or "here
is the exception traceback".

It is really all a matter of taste.


- Josiah

Jeff Shannon

unread,
Nov 3, 2004, 7:03:54 PM11/3/04
to
Josiah Carlson wrote:

>Zunbeltz Izaola <zunb...@wm.lc.ehu.es.XXX> wrote:
>
>
>>If I put some code after the .start() method the queue will be
>>empty. Do I need to insert a infinite loop to see when the exception
>>is raised?
>>
>>
>
>In your main thread (the one handling the GUI), you can set up a
>wx.Timer() whose bound event checks the queue every second or so.
>
>As another poster mentioned, you can also post events to the GUI, which
>can either say "there is an exception traceback in the queue" or "here
>is the exception traceback".
>
>

Another possibility is to check the queue in an idle-event handler.
When you place something in the queue, you can call wx.WakeUpIdle() to
ensure that the main thread will do idle processing at its soonest
opportunity.

Jeff Shannon
Technician/Programmer
Credit International

Josiah Carlson

unread,
Nov 3, 2004, 7:27:09 PM11/3/04
to Jeff Shannon, pytho...@python.org

The only ugly part about idle handling is that idle handlers are called
quite often (at least in the wxPython versions I've run). Specifically,
whenever I move my mouse over a wxPython app window, idle events are
streamed to the idle event handler at 30-60 events/second.

Not really a big deal, but something people should know about none the
less.


- Josiah

#source I used, lack of new namespace the result of copy/paste from
#wxPython wiki

from wxPython.wx import wxPySimpleApp, wxFrame, EVT_IDLE
import time

class myframe(wxFrame):
def __init__(self, *args, **kwargs):
wxFrame.__init__(self, *args, **kwargs)
self.t = None
EVT_IDLE(self, self.idle_handler)

def idle_handler(self, evt):
if self.t is None:
self.t = time.time()
else:
print round(time.time()-self.t, 3),
self.t = time.time()
evt.Skip()

app = wxPySimpleApp()
frame = myframe(None, -1, "Ugly Idle Handling")
frame.Show(1)
app.MainLoop()


#output on win2k, Python 2.3.2, wxPython 2.4.2.4 while wiggling my
#mouse over the window...

C:\Temp>test_idle.py
1.828 0.016 0.015 0.032 0.015 0.016 0.015 0.032 0.031 0.016 0.406 0.547 0.031 0.
016 0.015 0.016 0.015 0.016 0.016 0.015 0.016 0.016 0.015 0.016 0.015 0.016 0.01
6 0.015 0.016 0.016 0.015 0.016 0.015 0.016 0.015 0.016 0.016 0.015 0.016 0.015
0.016 0.016 0.015 0.016 0.016 0.015 0.016 0.015 0.016 0.016 0.015 0.016 0.016 0.
015 0.016 0.015 0.016 0.016 0.015 0.016 0.016 0.015 0.016 0.015 0.016 0.016 0.01
6 0.016 0.015 0.016 0.015 0.016 0.016 0.015 0.016 0.016 0.015 0.016 0.015 0.016
0.016 0.015 0.016 0.016 0.015 0.016 0.015 0.016 0.016 0.015 0.016 0.016 0.015 0.
016 0.015 0.016 0.016 0.015 0.016 0.015 0.016 0.015 0.016 0.016 0.015 0.016 0.01
6 0.015 0.016 0.015 0.016 0.016 0.015 0.016 0.016 0.015 0.016 0.015 0.016

C:\Temp>

Jeff Shannon

unread,
Nov 3, 2004, 8:38:41 PM11/3/04
to
Josiah Carlson wrote:

>Jeff Shannon <je...@ccvcorp.com> wrote:
>
>
>>Another possibility is to check the queue in an idle-event handler.
>>When you place something in the queue, you can call wx.WakeUpIdle() to
>>ensure that the main thread will do idle processing at its soonest
>>opportunity.
>>
>>
>
>The only ugly part about idle handling is that idle handlers are called
>quite often (at least in the wxPython versions I've run). Specifically,
>whenever I move my mouse over a wxPython app window, idle events are
>streamed to the idle event handler at 30-60 events/second.
>
>Not really a big deal, but something people should know about none the
>less.
>
>

True enough. I believe that in most cases, checking a queue for content
and acting if there's something there should be a fairly negligible
load, and that typically it's a load that happens only when nothing else
would be happening anyhow. But it *is* something to be aware of, and
one should be at least somewhat cautious about what's done during idle
processing. OTOH, it's a bit simpler to implement than creating a
custom event and installing a handler for that... Not *much* simpler, as
creating a custom event isn't exactly difficult, but in simple
circumstances it has its benefits... :)

(For those unfamiliar with wxPython's/wxWidgets' idle processing -- idle
events are triggered every time an application's event queue *becomes*
empty. This means that if you get a few mouse events, and process them,
then get a few more and process them... you get an idle event in between
each batch of mouse events, and possibly between each individual mouse
event depending on how fast your particular machine processes them. But
every time you get an idle event, it's because your app has nothing else
to deal with at that moment in time. It also means that if your window
is in the background and not getting any new events in the queue, the
queue will never switch from full to empty, and you won't get any idle
events. That's what wx.WakeUpIdle() is for -- to simulate the queue
emptying and thus triggering an idle event.)

Josiah Carlson

unread,
Nov 3, 2004, 10:01:57 PM11/3/04
to Jeff Shannon, pytho...@python.org

Jeff Shannon <je...@ccvcorp.com> wrote:
>
> Josiah Carlson wrote:
>
> >The only ugly part about idle handling is that idle handlers are called
> >quite often (at least in the wxPython versions I've run). Specifically,
> >whenever I move my mouse over a wxPython app window, idle events are
> >streamed to the idle event handler at 30-60 events/second.
> >
> >Not really a big deal, but something people should know about none the
> >less.
> >
> >
>
> True enough. I believe that in most cases, checking a queue for content
> and acting if there's something there should be a fairly negligible
> load, and that typically it's a load that happens only when nothing else
> would be happening anyhow. But it *is* something to be aware of, and
> one should be at least somewhat cautious about what's done during idle
> processing. OTOH, it's a bit simpler to implement than creating a
> custom event and installing a handler for that... Not *much* simpler, as
> creating a custom event isn't exactly difficult, but in simple
> circumstances it has its benefits... :)

Who said anything about a custom event? Timers are useful and so very
easy to use.

- Josiah


#example code...
from wxPython.wx import wxPySimpleApp, wxFrame, wxTimer, wxNewId, EVT_TIMER
import time

class myframe(wxFrame):
def __init__(self, *args, **kwargs):
wxFrame.__init__(self, *args, **kwargs)
self.t = None

tid = wxNewId()
self.tim = wxTimer(self, tid)
self.tim.Start(1000, False)
EVT_TIMER(self, tid, self.idle_handler)



def idle_handler(self, evt):
if self.t is None:
self.t = time.time()
else:
print round(time.time()-self.t, 3),
self.t = time.time()

app = wxPySimpleApp()

Jeff Shannon

unread,
Nov 3, 2004, 11:01:35 PM11/3/04
to
Josiah Carlson wrote:

>Who said anything about a custom event?
>

Several people earlier in this thread did -- Jaime Wyant and Michael
Hobbs, for instance. :)

So we have at least three viable ways of doing it, each with their own
advantages/disadvantages. Which one to use depends on circumstances and
personal preference...

Peter Hansen

unread,
Nov 4, 2004, 9:44:17 AM11/4/04
to
(Sorry if duplicate: NTTP server screwed up.)

Peter Hansen wrote:
> Thomas Heller wrote:
>> # raise exception in thread
>> print pythonapi.PyThreadState_SetAsyncExc(t, id(exc))
>
> I should note for the record that neither Thomas'
> example above nor even my more complex one

> ... implements the required API fully.

A further addendum, or a caution/concern:

The docs also say that you should own the GIL when
you call this routine. I suspect that in fact you
are supposed to have a lock on the GIL during the
_entire time_ that you are working with this routine,
specifically around all the following (pseudo)code:

n = PyThreadState_SetAsyncExc(id, Py_Object *)
if n > 1:
PyThreadState_SetAsyncExc(id, NULL)

I suspect that the only reason the second call works
(supposedly it "reverts the effects" of the first call)
is because the interpreter has not had time to execute
any more bytecodes in the affected threads. But since
the precise definition of this "reversion" is undocumented,
going to the source or an expert (which I'm clearly not)
would be the only way to sort out such "minor" issues...

-Peter

0 new messages