I'm having a problem calling Dialogs from a background thread, and was
hoping someone had some insight.
For the application I'm working on, I drop some long I/O bound tasks
into a background thread so as not to lock up the GUI for the minutes
that they take. Sometimes, however, I need to query the user for
information or ask them to do things. "Move this cable", "Enter this
serial number", etc.
I'm using the synchfunct recipe from "wxPython 2.8 Applicaton
Development Cookbook" to wrap up the ShowModal method in a call to
wx.CallAfter, so the actual displaying of and working with the Dialog
should be happening in the GUI thread. The problem is, this seems to
work for some dialogs (MessageDialog, FileDialog, ColourDialog) and not
with others (TextEntryDialog, NumberEntryDialog, custom classes). The
ones that don't work punt out with the message:
[xcb] Unknown request in queue while dequeuing
[xcb] Most likely this is a multi-threaded client and XInitThreads has
not been called [xcb] Aborting, sorry about that.
python: ../../src/xcb_io.c:178: dequeue_pending_request: Assertion
`!xcb_xlib_unknown_req_in_deq' failed. Aborted (core dumped)
I'd love to know why some work and some don't, but what I really need
is to know how to make this reliably work with any dialog box, whether
or not I understand it. I'm running wxPython 2.8.12.1, python 2.7.3,
and Xubuntu 12.04.
Demo code is attached; sorry about the length but I was trying to make
it self contained. The demo code is also using a variation on the book
recipe for purely copyright reasons; the technical idea is the same.
Points of interest are all flagged with 'NOTE:' for easy searching.
I'm having a problem calling Dialogs from a background thread, and was
hoping someone had some insight.
For the application I'm working on, I drop some long I/O bound tasks
into a background thread so as not to lock up the GUI for the minutes
that they take. Sometimes, however, I need to query the user for
information or ask them to do things. "Move this cable", "Enter this
serial number", etc.
I'm using the synchfunct recipe from "wxPython 2.8 Applicaton
Development Cookbook" to wrap up the ShowModal method in a call to
wx.CallAfter, so the actual displaying of and working with the Dialog
should be happening in the GUI thread.
Protecting ShowModal is not enough, you also need to run the code that creates the dialog in the UI thread. So instead of using synchfunct on just ShowModal you should create a method that does the whole interaction with the UI and the user, and protect it with synchfunct. For example:
@synchfunct
def askSomething(self):
with AskSomethingDialog(self) as dlg:
if dlg.ShowModal() == wx.ID_OK:
return self.doSomething()
return somethingElse
The problem is, this seems to
work for some dialogs (MessageDialog, FileDialog, ColourDialog) and not
with others (TextEntryDialog, NumberEntryDialog, custom classes). The
ones that don't work punt out with the message:
The ones that are working are stock system dialogs and typically they are not actually creating the dialog until the ShowModal is called.
Thanks for the help; that was the problem. In case anyone finds use
for it, I put that idea into a class that wraps up the underlying
dialog into a thread-safe operation.
class DialogWrapper(object):
"""
Base class to create a thread-safe wrapper for any Dialog.
Inherit from this, setting the following parameters:
dialog - A class derived from wx.Dialog
attrs - Dict of default attributes, if needed.
values - List of result values, if needed.
Call the ShowModal method of the derived class like you would
any other Dialog. The result of the ShowModal call will be
returned, and any values listed in the values field will be
copied from the underlying dialog to the safe object.
"""
@synchfunct
def ShowModal(self):
dlg = self.dialog(self.parent, *self.args,
**self.kwargs) ret = dlg.ShowModal()
for val in self.values:
setattr(self, val, getattr(dlg, val))
return ret
class SafeMessageDialog(DialogWrapper):
dialog = wx.MessageDialog
class SafeNumberEntryDialog(DialogWrapper):
dialog = wx.NumberEntryDialog
values = ['Value']
Again, greatly appreciated,
Rob
···
On Mon, 18 Mar 2013 12:22:44 -0700 Robin Dunn <robin@alldunn.com> wrote:
Rob Gaddi wrote:
> I'm having a problem calling Dialogs from a background thread, and was
> hoping someone had some insight.
>
> For the application I'm working on, I drop some long I/O bound tasks
> into a background thread so as not to lock up the GUI for the minutes
> that they take. Sometimes, however, I need to query the user for
> information or ask them to do things. "Move this cable", "Enter this
> serial number", etc.
>
> I'm using the synchfunct recipe from "wxPython 2.8 Applicaton
> Development Cookbook" to wrap up the ShowModal method in a call to
> wx.CallAfter, so the actual displaying of and working with the Dialog
> should be happening in the GUI thread.
Protecting ShowModal is not enough, you also need to run the code that
creates the dialog in the UI thread. So instead of using synchfunct on
just ShowModal you should create a method that does the whole
interaction with the UI and the user, and protect it with synchfunct.
For example:
@synchfunct
def askSomething(self):
with AskSomethingDialog(self) as dlg:
if dlg.ShowModal() == wx.ID_OK:
return self.doSomething()
return somethingElse
> The problem is, this seems to
> work for some dialogs (MessageDialog, FileDialog, ColourDialog) and not
> with others (TextEntryDialog, NumberEntryDialog, custom classes). The
> ones that don't work punt out with the message:
The ones that are working are stock system dialogs and typically they
are not actually creating the dialog until the ShowModal is called.
--
Rob Gaddi, Highland Technology
18 Otis St.
San Francisco, CA 94103
415-551-1700