原文:Threads/Concurrency with Python and the GNOME Platform
在主循环中不应该去做一些阻塞操作,因为主循环负责处理输入和绘制,阻塞它会导致用户界面卡死,对用户来说会导致用户的操作得不到任何响应。
可能的阻塞操作有以下几个:
- 从网络上加载一些图片资源
- 在本地文件系统里面查找一些东西
- 读写拷贝文件
- 一些运行时的计算依赖于其它因素
下面的例子演示了以下几点: - 和GTK并行执行的Python的线程如何和UI交互
- 如何在不使用线程的情况下将长时间运行的任务与GTK+事件处理交互
- 如何在glib中使用和控制异步I/O操作
Threads
第一个例子显示了使用Python线程去执行代码,但是窗口上的进度条可以正常运行。
import threading
import time
from gi.repository import GLib, Gtk, GObject
def app_main():
win = Gtk.Window(default_height=50, default_width=300)
win.connect("destroy", Gtk.main_quit)
progress = Gtk.ProgressBar(show_text=True)
win.add(progress)
def update_progress(i):
progress.pulse()
progress.set_text(str(i))
return False
def example_target():
for i in range(50):
GLib.idle_add(update_progress, i)
time.sleep(0.2)
win.show_all()
thread = threading.Thread(target=example_target)
thread.daemon = True
thread.start()
if __name__ == "__main__":
# Calling GObject.threads_init() is not needed for PyGObject >= 3.10.2
GObject.threads_init()
app_main()
Gtk.main()
这个例子展示了一个简单的窗口,窗口上有一个进度条组件。一且准备好之后会创建一个python的线程,然后Gtk的主循环运行起来,窗口依然是可以交互的。GLib.idle_add()传递了回调和接口,因为Gtk线程是不安全的,只有主线程才能更新ui。
GLib.idle_add
""" Adds a function to be called whenever there are no higher priority
events pending to the default main loop. The function is given the
default idle priority, #G_PRIORITY_DEFAULT_IDLE. If the function
returns %FALSE it is automatically removed from the list of event
sources and will not be called again.
See [memory management of sources][mainloop-memory-management] for details
on how to handle the return value and memory management of @data.
This internally creates a main loop source using g_idle_source_new()
and attaches it to the global #GMainContext using g_source_attach(), so
the callback will be invoked in whichever thread is running that main
context. You can do these steps manually if you need greater control or to
use a custom main context.
@param function: function to call
@param data: data to pass to @function.
@type function: SourceFunc
@type data: gpointer
@returns: the ID (greater than 0) of the event source.
@rtype: int
"""
在PyGObject 3.10.2之后,就不需要手动调用GObject.threads_init()
了。
Threads: The Deprecated Way
实际上支持多个线程调用GTK,但是这个支持已经弃用。下面是一个例子
import threading
import time
from gi.repository import Gtk, GObject, Gdk, GLib
def app_main_deprecated():
win = Gtk.Window(default_height=50, default_width=300)
win.connect("delete-event", Gtk.main_quit)
progress = Gtk.ProgressBar(show_text=True)
def example_target():
for i in range(50):
Gdk.threads_enter()
progress.pulse()
progress.set_text(str(i))
Gdk.threads_leave()
time.sleep(10)
def change_title_gdk(*args):
win.set_title("change_title_gdk")
return False
Gdk.threads_add_timeout_seconds(
GLib.PRIORITY_DEFAULT, 2, change_title_gdk, None)
def change_title_glib(*args):
Gdk.threads_enter()
win.set_title("change_title_glib")
Gdk.threads_leave()
return False
GLib.timeout_add_seconds(4, change_title_glib)
def change_title_click(button):
button.set_label("You clicked me")
button = Gtk.Button(label="Click Me")
button.connect("clicked", change_title_click)
box = Gtk.Box()
box.pack_start(button, False, True, 0)
box.pack_start(progress, True, True, 0)
win.add(box)
win.show_all()
thread = threading.Thread(target=example_target)
thread.daemon = True
thread.start()
if __name__ == "__main__":
# Calling GObject.threads_init() is not needed for PyGObject 3.10.2+
GObject.threads_init()
Gdk.threads_init()
Gdk.threads_enter()
app_main_deprecated()
Gtk.main()
Gdk.threads_leave()
GObject.threads_init()会创建一个全局锁,保证资源正常抢夺,调用Gdk.threads_enter()获取锁和Gdk.threads_leave()释放锁。
注意 这些方法在GTK3.6都已经遗弃了,参见Gdk Threads API
当主循环已经开始的时候,你有两种方法运行你的代码:
- 第一种方式是连接一个信号
- 第二种方式是在主循环里面添加调度函数,例如,使用
GLib.idle_add()
和GLib.timeout_add()
网友评论