PyGTK、线程和可访问性 - 应用程序挂起
我使用 PyGTK 创建了一些应用程序,并认为它们一直没问题,直到我在启用了辅助功能的环境(Ubuntu 上的 GNOME 和 Debian 上的 Openbox)中执行它们。我发现它们挂起,更令人沮丧的是它们导致 AT-SPI 应用程序挂起。
我创建了一个 repo ,其中包含使用 PyGTK 和线程的所有变体我可以想到:
- primarythread 前缀意味着 gtk.main() 在主线程内工作 (首先由 Python 执行),
- secondarythread 前缀表示 gtk.main() 在辅助线程内工作,
multiprocessing 前缀表示 import 和 gtk.main() 是在另一个进程中执行。
gobject 后缀表示仅使用了 gobject.threads_init(),
- gtkgdk后缀表示同时使用了gobject和gtk.gdk.threads_init(),
- import后缀表示执行了“import gtk”在新线程内。
showApps.py 是使用 AT-SPI 列出启用了辅助功能的应用程序的应用程序示例。
我在下表中总结了测试(也在 README 文件中):
"Hang" column indicates if the GTK application has hung.
"Listed" column indicates if the application is visible in the AT-SPI
listing.
| hang | listed |
----------------------------------+----------+--------+
primarythread_gobject.py | no | yes |
primarythread_gtkgdk.py | no | yes |
secondarythread_gobject_import.py | no [1] | yes |
secondarythread_gobject.py | yes | hang |
secondarythread_gtkgdk_import.py | no [1] | yes |
secondarythread_gtkgdk.py | yes | hang |
multiprocessing_gobject.py | no | yes |
[1] ** (secondarythread_gobject_import.py:5828): CRITICAL **:
giop_thread_request_push: assertion `tdata != NULL' failed
-- at the application termination
当 AT-SPI 被标记为“挂起”时,它会在应用程序列出期间无条件挂起。
例如,在窗口失去焦点和重新获得焦点后,PyGTK 应用程序会发生挂起。
测试表明,当 gtk.main() 从第一个/主 Python 线程运行时,不会出现任何问题。但这并不能令我满意,因为我不喜欢将 GUI 视为应用程序的主要部分。
我的问题是:
- 标记为辅助线程的程序中的代码有什么问题吗?还是 GTK/GAIL/AT-SPI 中的错误?
- 是否有禁止在第一个/主 Python 线程之外运行 gtk.main() 的策略?
I've created some applications using PyGTK and thought they had been OK until I executed them in an environment with accessibility enabled (GNOME on Ubuntu and Openbox on Debian). I've found out they hang and what is more frustrating - they cause AT-SPI applications to hang.
I've created a repo with all variants of using PyGTK with threads I could think of:
- primarythread prefix means gtk.main() works inside the primary thread
(one executed first by Python), - secondarythread prefix means gtk.main() works inside the secondary thread,
multiprocessing prefix means import and gtk.main() are executed in another process.
gobject suffix means only gobject.threads_init() has been used,
- gtkgdk suffix means both gobject and gtk.gdk.threads_init() have been used,
- import suffix means "import gtk" is executed inside the new thread.
showApps.py is an example of an application using AT-SPI to list applications with accessibility enabled.
I've summarized tests in the table below (also inside the README file):
"Hang" column indicates if the GTK application has hung.
"Listed" column indicates if the application is visible in the AT-SPI
listing.
| hang | listed |
----------------------------------+----------+--------+
primarythread_gobject.py | no | yes |
primarythread_gtkgdk.py | no | yes |
secondarythread_gobject_import.py | no [1] | yes |
secondarythread_gobject.py | yes | hang |
secondarythread_gtkgdk_import.py | no [1] | yes |
secondarythread_gtkgdk.py | yes | hang |
multiprocessing_gobject.py | no | yes |
[1] ** (secondarythread_gobject_import.py:5828): CRITICAL **:
giop_thread_request_push: assertion `tdata != NULL' failed
-- at the application termination
When AT-SPI is marked "hang" it hangs unconditionally during applications listing.
Hanging of the PyGTK application happens for example after losing and regaining focus by its window.
Test showed that no problems occur when gtk.main() is run from the first/main Python thread. But this doesn't satisfy me as I don't like treating GUI as the main part of the application.
My questions are:
- Is there anything wrong with code in programs marked as secondarythread? Or is it a bug in GTK/GAIL/AT-SPI?
- Is there a policy that prohibits running gtk.main() outside the first/main Python thread?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我知道的唯一策略是任何时候只有一个线程可以访问 GTK 库。
如果您希望 GUI 在其自己的辅助线程中运行,那么您需要确保这是访问 GTK 的唯一线程,因为 GTK 不是线程安全的。这就是为什么在辅助线程的情况下,它仅在从线程内导入 GTK 时才起作用。如果它是在线程外部加载的,那么从技术上讲,主线程也在某种程度上使用它,并且两个线程都可以尝试同时访问该库。您的主线程案例工作的原因是因为您正确锁定了对GTK 库具有 gtk.gdk.threads_enter() 和threads_leave()。我认为你可以在 secondarythread_gtkgdk_import.py 中摆脱它们并且它会起作用,因为只有一个线程知道 GTK 已被加载。
现在,这只是我的猜测,因为我对 AT-SPI 不太了解。由于您已经在 secondarythread_gobject.py 和 secondarythread_gtkgdk.py 中的两个单独线程中引发了竞争条件,因此 AT-SPI 也可能会以某种方式受到此状态的影响。
如果您确实希望 GUI 位于辅助线程中,请使用 secondarythread_gtkgdk_import.py (可能会删除不必要的threads_enter() 和threads_leave())。我建议您将 GUI 作为主线程,并在子线程中启动所有后台进程。
比较以下两个示例:
在第一种情况下,从主线程导入
time
,然后生成一个子进程。子进程也可以访问time
,因此时间会被打印两次。在第二种情况下,time
被导入到子线程中。然而,父线程看不到这一点,因此对time.ctime()
的第二次调用失败。当您从子线程加载 GTK 时,父线程对此一无所知,因此您永远不会遇到两个线程尝试访问 GTK 库的问题(引用 GDK 文档:“也就是说,只有一个线程可以在任何给定时间。”)。
The only policy that I am aware of is that only one thread may access the GTK libraries at any time.
If you want the GUI to run in its own, secondary thread, then you need to be sure that that is the only thread that accesses GTK because GTK is not thread-safe. That's why in your secondarythread cases, it only works when GTK is imported from within the thread. If it's loaded outside of the thread, then technically the primary thread is also using it to some extent and both threads could try to access the library at the same time.. The reason your primarythread cases work is because you're correctly locking access to the GTK libraries with gtk.gdk.threads_enter() and threads_leave(). I think you could get rid of them in secondarythread_gtkgdk_import.py and it'll work, since there's only one thread that's aware of GTK having been loaded.
Now, this is speculation on my part since I don't know much about AT-SPI. Since you're already causing race conditions from your two separate threads in secondarythread_gobject.py and secondarythread_gtkgdk.py, AT-SPI may also somehow be affected by this state.
If you really want your GUI in a secondary thread, go with secondarythread_gtkgdk_import.py (possibly removing the threads_enter() and threads_leave() as unnecessary). I would recommend instead having your GUI as the primary thread and having all of your background processes be launched in child threads.
Compare the following two examples:
In the first case,
time
is imported from the main thread and then a child process is spawned. The child process also has access totime
as well, so the time is printed twice. In the second case,time
is imported in the child thread. The parent thread, however, does not see this, and so the second call totime.ctime()
fails.When you load GTK from the child thread, the parent thread is ignorant of it, so you never run into problems with two threads trying to access the GTK libraries (to quote the GDK documentation: "That is, only one thread can use GTK+ at any given time.").