在 Gtk Python 中的线程处理

4

我正在忙于编写一个应用程序,在一定时间后需要从网站检查更新,我使用带有Gtk +3的Python。

main.py文件

class Gui:
    ...
    def on_update_click():
        update()

app=Gui()
Gtk.main()

更新.py文件
def update():
    #check site for updates
    time.sleep(21600) #check again in 6hrs

我认为我需要使用线程。

我的想法是:

Gtk.main() 运行主线程。

当用户点击更新按钮时,update() 在后台运行。#线程2

我的想法正确吗?还是我漏掉了什么?

编辑: 好的, on_update_click 函数:

            Thread(target=update).start(). 

K,电脑不再冻结 :D

现在的情况是只有当我关闭Gtk.main()时,更新线程才会启动。虽然UI关闭后它能够持续更新是好的,但我也希望它在UI打开时就开始更新。


on_update_click()缺少self参数。 - jfs
3个回答

7

我终于成功让它工作了。 我需要说:

from gi.repository import Gtk,GObject

GObject.threads_init()
Class Gui:
    .....
    ......
    def on_update_click():
            Thread(target=update).start()

一开始我使用的是:

thread.start_new_thread(update())

在 on_update_click 函数中,正如 J.F Sebastian 所述,这样做是不正确的,因为它会立即调用此线程,导致整个计算机冻结。

然后我只是添加了:

Thread(target=update).start()

在主线程 Gtk.main() 关闭后,on_update_clicked 函数只能运行一次。因此,线程不能同时运行。

通过添加以下内容: GObject.threads_init()

这允许线程按顺序运行到 Python 解释器中: Gtk 中的线程


4

thread.start_new_thread(update())是错误的。它会立即在主线程中调用update(),你不应该直接使用thread模块,而应该使用threading模块。

你可以调用threading.current_thread()来找出哪个线程执行了update()

为了简化你的代码,你可以在主线程中运行所有gtk代码,并使用阻塞操作来检索网页并在后台线程中运行它们。

基于GTK+ 3教程中的扩展示例

#!/usr/bin/python
import threading
import urllib2
from Queue import Queue

from gi.repository import Gtk, GObject

UPDATE_TIMEOUT = .1 # in seconds

_lock = threading.Lock()
def info(*args):
    with _lock:
        print("%s %s" % (threading.current_thread(), " ".join(map(str, args))))

class MyWindow(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="Hello World")

        self.button = Gtk.Button(label="Click Here")
        self.button.connect("clicked", self.on_button_clicked)
        self.add(self.button)

        self.updater = Updater()
        self._update_id = None
        self.update()

    def on_button_clicked(self, widget):
        info('button_clicked')
        self.update()

    def update(self):
        if self._update_id is not None: 
            GObject.source_remove(self._update_id)

        self.updater.add_update(self.done_updating) # returns immediately
        # call in UPDATE_TIMEOUT seconds
        self._update_id = GObject.timeout_add(
            int(UPDATE_TIMEOUT*1000), self.update)

    def done_updating(self, task_id):
        info('done updating', task_id)
        self.button.set_label("done updating %s" % task_id)


class Updater:
    def __init__(self):
        self._task_id = 0
        self._queue = Queue(maxsize=100) #NOTE: GUI blocks if queue is full
        for _ in range(9):
            t = threading.Thread(target=self._work)
            t.daemon = True
            t.start()

    def _work(self):
        # executed in background thread
        opener = urllib2.build_opener()
        for task_id, done, args in iter(self._queue.get, None):
            info('received task', task_id)
            try: # do something blocking e.g., urlopen()
                data = opener.open('http://localhost:5001').read()
            except IOError:
                pass # ignore errors

            # signal task completion; run done() in the main thread
            GObject.idle_add(done, *((task_id,) + args))

    def add_update(self, callback, *args):
        # executed in the main thread
        self._task_id += 1
        info('sending task ', self._task_id)
        self._queue.put((self._task_id, callback, args))

GObject.threads_init() # init threads?

win = MyWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()

Gtk.main()

注意:GObject.idle_add() 是唯一从不同线程调用的与 gtk 相关的函数。
另请参见 多线程 GTK 应用程序 - 第 1 部分:误解

好的,在on_update_click函数中添加了。 - zeref

0

线程是解决问题的第一种方式。您可以创建线程并在该线程中运行长时间阻塞的函数(这样您的GUI就不会挂起)。

另一种方法是使用异步网络,例如使用python-gio(GObject-IO)或另一个具有与GLib主循环一起工作的可能性的库(就像Twisted一样)。这种方法有点不同,使用非阻塞套接字操作。当来自套接字(您正在轮询的站点)的数据可供读取时,您的主循环将进行回调。不幸的是,GIO没有高级HTTP API,因此您可以使用{{link2:GSocketClient}}并手动创建HTTP请求结构。


我不明白你所指的是什么:运行长时间阻塞函数。 - zeref
连接到网站并获取页面内容可能是一个长时间运行的操作 - 长达几秒钟。 如果您使用 urllib 进行此操作,您的 GUI(应用程序现在是单线程的)将在更新函数执行期间冻结。这意味着阻塞:GUI 事件处理程序无法在更新完成之前执行。因此,我建议要么在线程中运行 update(),要么使用异步(非阻塞)网络操作。 - Rostyslav Dzinko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接