Multi-thread logging to textctrl

Hi!
    I encounter a race condition in wx and logging library. In my
program, there is a textctrl in mainframe widget. There are two
threads (one is main loop, the other is a working thread) continuously
writing logtext by using logging library.

class OutputWindowStream(object):
    def __init__(self, textbox):
        # textbox presents the instance of textctrl in the mainframe
        self.textbox = textbox

    def write(self, s):
        # treat the textbox as DebugWindow. logtext will append to it.
        self.textbox.AppendText(s)

    def flush(self):
        pass

In the MainFrame.__init__, a StreamHandler is attached to root logger
in order to redirect logtext into the textbox:
        hdler = logging.StreamHandler(OutputWindowStream(self.textbox))
        self.log = logging.getLogger()
        self.log.addHandler(hdler)
Then, the mainframe start a new working thread:
        self.worker = Worker() #
        self.worker.log = self.log
        self.worker.setDaemon(True)
        self.worker.start()
Later, both mainframe and worker start to generate output text by:
        self.log.info(...)
However, this program will deadlock randomly. If I remove the
self.log.info in one thread, it runs smoothly. Therefore, the deadlock
is made by logging library while concurrently writing text into a
textctrl widget. But logging library indeed synchronize the access to
OutputWindowStream by internal lock. I'm confused why the race
condition still occurs.

···

---
ShenLei

However, this program will deadlock randomly. If I remove the

self.log.info
in one thread, it runs smoothly. Therefore, the deadlock
is made by logging library while concurrently writing text into a
textctrl widget. But logging library indeed synchronize the access to
OutputWindowStream by internal lock. I’m confused why the race

condition still occurs.

You can’t call methods on wx objects in any thread but the main thread, so by calling ‘self.textbox.write(…)’ in both threads all hell might break lose.

That’s easy to get around, though. Just use wx.CallAfter, e.g.:

wx.CallAfter(wx.textbox.write, ‘text’)

wx.CallAfter will schedule the given callable and arguments to be run right at the end of the current event loop.

···


Stephen Hansen
Development
Advanced Prepress Technology

shansen@advpubtech.com
(818) 748-9282

You are updating the GUI from a thread other than main, and if you need to do
this you will certainly have to take care notifying the main thread that a
GUI update is pending.

Otherwise you will get a race-condition in the GUI, which you experience as if
the application hangs.

Best regards,
Frank

···

On Thursday 11 October 2007 07:44:48 甜瓜 wrote:

But logging library indeed synchronize the access to
OutputWindowStream by internal lock. I'm confused why the race
condition still occurs.

Thank you very much! The bug is solved.
Is this a strict rule for wx that only the main thread (or MainLoop)
can operate on a widget? It seems that wx.MainLoop do some thing
asynchronously. AppendText returned while wx do repaint in background.
Therefore, even if all access to the textbox is synchronized by
logging package, it still has deadlock.

···

---
ShenLei

from Multithreading overview in wx docs:

If you do decide to use threads in your application, it is strongly recommended that no more than one thread calls GUI functions. The thread sample shows that it is possible for many different threads to call GUI functions at once (all the threads created in the sample access GUI), but it is a very poor design choice for anything except an example. The design which uses one GUI thread and several worker threads which communicate with the main one using events is much more robust and will undoubtedly save you countless problems (example: under Win32 a thread can only access GDI objects such as pens, brushes, &c created by itself and not by the other threads).

take a look at Threads example from the Demo… that’s the recommended way…
A admit it is more convoluted than a quick hack BUT it is actually simpler if you look in perspective. The time you waste trying to debug this kind of situations far outstretches the time used to implement a “standard” way to handle IPC in wxPython.

Peter

···

On 10/11/07, 甜瓜 littlesweetmelon@gmail.com wrote:

Thank you very much! The bug is solved.
Is this a strict rule for wx that only the main thread (or MainLoop)
can operate on a widget? It seems that wx.MainLoop do some thing
asynchronously. AppendText returned while wx do repaint in background.

Therefore, even if all access to the textbox is synchronized by
logging package, it still has deadlock.


ShenLei


To unsubscribe, e-mail: wxPython-users-unsubscribe@lists.wxwidgets.org
For additional commands, e-mail: wxPython-users-help@lists.wxwidgets.org


There is NO FATE, we are the creators.

Is this a strict rule for wx that only the main thread (or
MainLoop) can operate on a widget?

Yes.

There are probably operations that can be asyncronous. But
AFAIK, that's not gauranteed behavior, so counting on anything
working asyncronously is asking for trouble.

It seems that wx.MainLoop do some thing asynchronously.
AppendText returned while wx do repaint in background.

I don't know what you mean.

Therefore, even if all access to the textbox is synchronized
by logging package, it still has deadlock.

Please post an example that exhibits such a deadlock.

···

On 2007-10-11, =?GB2312?B?zPC5zw==?= <littlesweetmelon@gmail.com> wrote:

--
Grant Edwards grante Yow! Thousands of days of
                                  at civilians ... have produced
                               visi.com a ... feeling for the
                                                   aesthetic modules --

Thank you very much! The bug is solved.
Is this a strict rule for wx that only the main thread (or MainLoop)
can operate on a widget? It seems that wx.MainLoop do some thing
asynchronously. AppendText returned while wx do repaint in background.

Yes, it’s a strict rule; sometimes something works, but ultimately
it’ll fail if you don’t abide by it. In the main thread, everything is
happening in the context of an event loop. So wx has a known-state it
can deal with, since you’re at a certain point in the event queue and
when you’re done it can continue on. If you call wx functions from
another thread, you change that expected state. So it can get all
confused and go crashy.

Making wx able to handle this all itself would add overhead that may
not be necessary all the time… it’d have to queue everything up
magically w/ wx.CallAfter (or some other mechanism), or detect you’re
in a different thread, etc, etc… and in the end? It’s so easy to work
around this problem that its not necessary, and adding overhead to
everything would be bad. wx.CallAfter is a gem.

Therefore, even if all access to the textbox is synchronized by

logging package, it still has deadlock.

I’m not entirely sure what you’re saying here.

···

On 10/11/07, 甜瓜 littlesweetmelon@gmail.com wrote:

甜瓜 wrote:

Thank you very much! The bug is solved.
Is this a strict rule for wx that only the main thread (or MainLoop)
can operate on a widget? It seems that wx.MainLoop do some thing
asynchronously. AppendText returned while wx do repaint in background.

No the repaint happens in another event, so it is synchronous. If you don't catch that event yourself then it might appear to be happening in the background, but in reality the widget is waiting until it gets the event and then it will paint itself and then it returns to the MainLoop.

Therefore, even if all access to the textbox is synchronized by
logging package, it still has deadlock.

But if the logging package is sending the text to the widget from a non-main thread then you'll still have the multi-thread UI issues. You need to do all access or modification of UI objects from the main thread. As Peter pointed out, even if some platforms are thread-safe in the UI, some limit access to some objects to the thread they were created on so even if it is safe to call something the platform may just ignore it.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!