Gtk.StatusIcon 在多线程应用程序中冻结
Gtk.StatusIcon freezing in multi-threaded app
我在 CentOs/RH 上显示来自 API 的下载状态的应用程序有问题。使用 PyCharm IDE 一切正常,但在使用 PyInstaller(一个文件夹)编译后,应用程序非常不稳定,我找不到错误。我 运行 线程并每 10 秒下载 API 状态,如果有变化,我会更新图标并发送通知。左键点击图标后,状态显示在Gtk.ApplicationWindow
.
有时 Gtk.StatusIcon
无法更改状态或处于非活动状态 - 左/右单击不起作用(但会收到状态更改通知)
应用程序可能会意外结束
我怀疑问题出在线程中,但我找不到合适的解决方案。
代码 (main.py)
class ExampleSystemTrayInit(Gtk.StatusIcon):
def __init__(self):
super().__init__()
app.tray = Gtk.StatusIcon()
app.tray.set_from_file(ico_start)
app.tray.connect('popup-menu', self.on_right_click)
app.tray.connect('activate', self.on_left_click, app)
def on_right_click(self, icon, event_button, event_time):
self.menu = Gtk.Menu()
quit = Gtk.MenuItem("Quit")
quit.connect('activate', self.quitApp)
self.menu.append(quit)
self.menu.show_all()
self.menu.popup(None, None, Gtk.StatusIcon.position_menu, app.tray, event_button, event_time)
def on_left_click(self, icon, app):
try:
self.app = app
if app.all_status:
data = self.app.all_status
Controller.show_window(self, data, self.app, refresh=False)
#call to class MainWindow(Gtk.ApplicationWindow) and show some data
else:
pass;
except Exception as e:
print(e)
app.tray.set_from_file(ico_disconnect)
def quitApp(self, par):
app.quit()
class ExampleSystemTray(Gtk.Application):
def __init__(self, *args, **kwargs):
super().__init__(
application_id="example-system-tray2.app"
)
self.tray = None
self.mainWindow = None
def do_activate(self):
if not hasattr(self, "my_app"):
self.hold()
self.my_app_settings = "Primary application instance."
self.systray = ExampleSystemTrayInit()
TrayController(app)
else:
print("Already running!")
def do_startup(self):
Gdk.threads_init()
Gdk.threads_enter()
Gtk.Application.do_startup(self)
Gdk.threads_leave()
if __name__ == "__main__":
GObject.threads_init()
app = ExampleSystemTray()
app.run()
托盘控制器(traycontroller.py)
class TrayController(threading.Thread):
def __init__(self, app):
self.app = app
self.cache = None
threading.Thread.__init__(self, name='TrayController', )
self.interval = 10
self.finished = threading.Event()
self.daemon = True
self.start()
def run(self):
while True:
try:
self.finished.wait(self.interval)
if not self.finished.is_set():
self.connect(self.app)
except Exception as e:
print(e)
def cancel(self):
"""Stop the timer if it hasn't finished yet."""
self.finished.set()
def connect(self, app):
try:
self.result = GetJson.get_json(self, api_view)
if self.result == None:
self.cache = None
self.show_status(2)
app.all_status = None
elif (self.cache != self.result):
self.cache = self.result
app.all_status = AllStatusInstance(self.result)
self.show_status(app.all_status.status)
Controller.show_main_window(self, app.all_staus, app, refresh=True)
#call to class MainWindow(Gtk.ApplicationWindow) if window is visble, then refresh
except Exception as e:
print(e)
self.show_status(2)
app.all_status = None
def show_status(self, status):
self.status = status
if self.status == 0:
return self.change_tray_icon(ico_red, notify_red, tag_red, widget=Gtk.StatusIcon)
elif self.status == 1:
return self.change_tray_icon(ico_gray, notify_gray, tag_gray, widget=Gtk.StatusIcon)
elif self.status == 2:
return self.change_tray_icon(ico_disconnect, notify_disconnect, tag_disconnect,
widget=Gtk.StatusIcon)
def change_tray_icon(self, icon, notification, tag, widget):
if self.app.tray.get_title() != tag:
self.app.tray.set_from_file(icon)
self.app.tray.set_title(tag)
self.notify("Notification:", notification, icon)
def notify(self, title, body, link):
notify2.init("Alert", mainloop=None)
icon = GdkPixbuf.Pixbuf.new_from_file(link)
n = notify2.Notification(title, body)
n.set_icon_from_pixbuf(icon)
n.set_urgency(notify2.URGENCY_CRITICAL)
n.show()
class AllStatusInstance(object):
__instance = None
def __new__(cls, val):
if AllStatusInstance.__instance is None:
AllStatusInstance.__instance = object.__new__(cls)
AllStatusInstance.__instance.val = val
return AllStatusInstance.__instance
事实上,问题出在线程上,因为您不能从多个线程使用 GTK API,永远,因为 the documentation clearly states。
您应该使用 Gio.Task
if you have a blocking, synchronous operation and you wish to update some UI state at the end of it; or, if you're using a thread object in Python, always use GLib.MainContext.invoke_full()
在与 运行 GTK 主循环相同的线程中调用一个函数。
你应该不做的是:
def do_startup(self):
Gdk.threads_init()
Gdk.threads_enter()
Gtk.Application.do_startup(self)
Gdk.threads_leave()
然后从一个单独的线程而不是 运行 GTK 的事件循环调用 GTK API;那是完全未定义的,non-portable 行为。
我在 CentOs/RH 上显示来自 API 的下载状态的应用程序有问题。使用 PyCharm IDE 一切正常,但在使用 PyInstaller(一个文件夹)编译后,应用程序非常不稳定,我找不到错误。我 运行 线程并每 10 秒下载 API 状态,如果有变化,我会更新图标并发送通知。左键点击图标后,状态显示在Gtk.ApplicationWindow
.
有时
Gtk.StatusIcon
无法更改状态或处于非活动状态 - 左/右单击不起作用(但会收到状态更改通知)应用程序可能会意外结束
我怀疑问题出在线程中,但我找不到合适的解决方案。
代码 (main.py)
class ExampleSystemTrayInit(Gtk.StatusIcon):
def __init__(self):
super().__init__()
app.tray = Gtk.StatusIcon()
app.tray.set_from_file(ico_start)
app.tray.connect('popup-menu', self.on_right_click)
app.tray.connect('activate', self.on_left_click, app)
def on_right_click(self, icon, event_button, event_time):
self.menu = Gtk.Menu()
quit = Gtk.MenuItem("Quit")
quit.connect('activate', self.quitApp)
self.menu.append(quit)
self.menu.show_all()
self.menu.popup(None, None, Gtk.StatusIcon.position_menu, app.tray, event_button, event_time)
def on_left_click(self, icon, app):
try:
self.app = app
if app.all_status:
data = self.app.all_status
Controller.show_window(self, data, self.app, refresh=False)
#call to class MainWindow(Gtk.ApplicationWindow) and show some data
else:
pass;
except Exception as e:
print(e)
app.tray.set_from_file(ico_disconnect)
def quitApp(self, par):
app.quit()
class ExampleSystemTray(Gtk.Application):
def __init__(self, *args, **kwargs):
super().__init__(
application_id="example-system-tray2.app"
)
self.tray = None
self.mainWindow = None
def do_activate(self):
if not hasattr(self, "my_app"):
self.hold()
self.my_app_settings = "Primary application instance."
self.systray = ExampleSystemTrayInit()
TrayController(app)
else:
print("Already running!")
def do_startup(self):
Gdk.threads_init()
Gdk.threads_enter()
Gtk.Application.do_startup(self)
Gdk.threads_leave()
if __name__ == "__main__":
GObject.threads_init()
app = ExampleSystemTray()
app.run()
托盘控制器(traycontroller.py)
class TrayController(threading.Thread):
def __init__(self, app):
self.app = app
self.cache = None
threading.Thread.__init__(self, name='TrayController', )
self.interval = 10
self.finished = threading.Event()
self.daemon = True
self.start()
def run(self):
while True:
try:
self.finished.wait(self.interval)
if not self.finished.is_set():
self.connect(self.app)
except Exception as e:
print(e)
def cancel(self):
"""Stop the timer if it hasn't finished yet."""
self.finished.set()
def connect(self, app):
try:
self.result = GetJson.get_json(self, api_view)
if self.result == None:
self.cache = None
self.show_status(2)
app.all_status = None
elif (self.cache != self.result):
self.cache = self.result
app.all_status = AllStatusInstance(self.result)
self.show_status(app.all_status.status)
Controller.show_main_window(self, app.all_staus, app, refresh=True)
#call to class MainWindow(Gtk.ApplicationWindow) if window is visble, then refresh
except Exception as e:
print(e)
self.show_status(2)
app.all_status = None
def show_status(self, status):
self.status = status
if self.status == 0:
return self.change_tray_icon(ico_red, notify_red, tag_red, widget=Gtk.StatusIcon)
elif self.status == 1:
return self.change_tray_icon(ico_gray, notify_gray, tag_gray, widget=Gtk.StatusIcon)
elif self.status == 2:
return self.change_tray_icon(ico_disconnect, notify_disconnect, tag_disconnect,
widget=Gtk.StatusIcon)
def change_tray_icon(self, icon, notification, tag, widget):
if self.app.tray.get_title() != tag:
self.app.tray.set_from_file(icon)
self.app.tray.set_title(tag)
self.notify("Notification:", notification, icon)
def notify(self, title, body, link):
notify2.init("Alert", mainloop=None)
icon = GdkPixbuf.Pixbuf.new_from_file(link)
n = notify2.Notification(title, body)
n.set_icon_from_pixbuf(icon)
n.set_urgency(notify2.URGENCY_CRITICAL)
n.show()
class AllStatusInstance(object):
__instance = None
def __new__(cls, val):
if AllStatusInstance.__instance is None:
AllStatusInstance.__instance = object.__new__(cls)
AllStatusInstance.__instance.val = val
return AllStatusInstance.__instance
事实上,问题出在线程上,因为您不能从多个线程使用 GTK API,永远,因为 the documentation clearly states。
您应该使用 Gio.Task
if you have a blocking, synchronous operation and you wish to update some UI state at the end of it; or, if you're using a thread object in Python, always use GLib.MainContext.invoke_full()
在与 运行 GTK 主循环相同的线程中调用一个函数。
你应该不做的是:
def do_startup(self):
Gdk.threads_init()
Gdk.threads_enter()
Gtk.Application.do_startup(self)
Gdk.threads_leave()
然后从一个单独的线程而不是 运行 GTK 的事件循环调用 GTK API;那是完全未定义的,non-portable 行为。