Gtk.Spinner 在 Python GTK 中导入大型库时

Gtk.Spinner in Python GTK while importing large library

我有一个 C 语言的 GTK 应用程序,它将生成一个 Python GTK 进程,使用 GtkSocket/GtkPlug(使用 XEmbed 协议)将 matplotlib 图嵌入到 C 进程中的 window 中.我遇到的问题是 matplotlib 库的导入大约需要 2.5 秒,在此期间套接字小部件只是透明的。我想在导入 matplotlib 之前在插件中放置一个 Gtk.Spinner(因此是 Python 一侧),并在导入 matplotlib 库的过程中让微调器异步设置动画。问题在于,为了将小部件放置在插件中,随后,为了使 Gtk.Spinner 具有动画效果,它需要 GTK 主循环的迭代。我从很多不同的角度来探讨这个问题:

(1) 使用线程。第一次尝试是尝试通过线程 运行 Gtk.main_iteration() ,但是,GTK 只能在主线程上 运行 而这不起作用。它会使程序停止。

(2) 然后我尝试从线程使用 GObject.idle_add,其中主循环迭代将从空闲函数 运行 (显然通过空闲调用的函数是在主线程上完成的?),但这也不起作用。

(3) 然后我尝试在线程上导入模块,而主线程 运行s Gtk.main_iteration() 允许微调器在导入时旋转地方。这个想法是一旦导入完成,一个布尔标志就会改变以触发主迭代循环的中断。在这种情况下,微调器出现并旋转,但情节从未出现。我收到 X 服务器错误:

Gdk-WARNING **: master: Fatal IO error 104 (Connection reset by peer) on X server :0.

(4) 代替线程,我尝试使用 GObject.timeout_add 定期调用将执行 Gtk.main_iteration() 的函数,但这样做会导致原始行为,其中socket/plug 在绘图出现之前是透明的(即没有旋转器出现也没有旋转)。

我 运行 没有想法,现在我来这里希望得到帮助。他们的关键想法是在 Python 脚本加载 matplotlib 库时让 Gtk.Spinner 旋转,一旦完成,用图形替换微调器小部件(虽然所有这些都发生在GtkSocket/Plug)。我没有为此创建一个最小的可重现示例,因为在这种情况下它会相当复杂,但是如果有人愿意提供帮助,我可以提出一个。但是,相关代码部分如下(之前的尝试已被注释掉):

import sys
import gi
import time
import threading
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GObject
from gi.repository import Pango as pango

if sys.platform != "win32":

    GObject.threads_init()
    Gdk.threads_init()

    # Open socket ID file, read Socket ID into variable, close file
    socketFile = open('resources/com/gtkSocket', 'r+')
    gtkSock = socketFile.read()
    print("The ID of the sockets window in Python is: ", int(gtkSock))
    socketFile.close()
    # Create plug, create GTK box, add box to plug, add spinner to box
    spin_plug = Gtk.Plug.new(int(gtkSock))
    socketbox = Gtk.Box()
    spin_plug.add(socketbox)

    spinner = Gtk.Spinner()
    socketbox.pack_start(spinner, expand=True, fill=True, padding=False)
    spinner.start()

    finished = False

    def thread_run():
        time.sleep(4)
        '''
        # Loop for four seconds checking if gtk events are pending, and if so, main loop iterate
        t_end = time.time() + 4
        while time.time() < t_end:
            if (Gtk.events_pending()):
                Gtk.main_iteration()
                print("Events Pending...")
        '''

        '''
        import argparse
        import collections
        import csv
        import matplotlib
        import matplotlib.pyplot as plt
        import matplotlib.patches as patches
        import matplotlib.lines as mlines

        from collections import defaultdict
        from enum import Enum
        from matplotlib.backend_bases import MouseEvent
        from matplotlib.pyplot import draw
        from matplotlib.widgets  import SpanSelector
        from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FC
        from matplotlib.backends.backend_gtk3 import NavigationToolbar2GTK3
        '''

        # You cannot run GTK Code on a separate thread from the one running the main loop
        # Idle add allows scheduling code to be executed on the main thread
        GObject.idle_add(cleanup)

    def cleanup():
        # Note: Trying to add the Gtk Main Iterations to the idle add function did not work...
        print("Closing Spinner Thread...")
        spinner.stop()
        finished = True
        thread.join()

    # start a separate thread and immediately return to main loop
    #thread = threading.Thread(target=thread_run)
    #thread.start()
    spin_plug.show_all()

    
    def spin():
        busy_wait = 0
        while (Gtk.events_pending() or busy_wait < 10):
            Gtk.main_iteration()
            if (not Gtk.events_pending()):
                busy_wait = busy_wait + 1
        print("Spin Call complete.")
        return True

    GObject.timeout_add(50, spin)
    
    '''
    # We cannot simply run an infinite Gtk.main() loop, so iterate until the plug has been filled
    busy_wait = 0
    while (Gtk.events_pending() or busy_wait < 10):
        if (finished):
            break
        print("Busy Wait: %d" % busy_wait)
        Gtk.main_iteration()
        if (not Gtk.events_pending()):
            busy_wait = busy_wait + 1
    print("Gtk Main Loop iterations complete.")
    '''

如有任何指点或想法,我们将不胜感激。

解决方案是在线程上执行导入,同时允许主线程执行主循环迭代。简单地做“导入”是行不通的。以前的一些有用的 Stack Overflow 帖子在这里:

Python thread for pre-importing modules

import a module from a thread does not work

解决方案如下所示:

GObject.threads_init()
Gdk.threads_init()

# Open socket ID file, read Socket ID into variable, close file
socketFile = open('resources/com/gtkSocket', 'r+')
gtkSock = socketFile.read()
print("The ID of the sockets window in Python is: ", int(gtkSock))
socketFile.close()
# Create plug, create GTK box, add box to plug, add figure to box
spin_plug = Gtk.Plug.new(int(gtkSock))
socketbox = Gtk.Box()
spin_plug.add(socketbox)
# Create a spinner, pack it, and start it spinning
spinner = Gtk.Spinner()
socketbox.pack_start(spinner, expand=True, fill=True, padding=False)
spinner.start()
spinner.show()
# Flag to break from the Gtk.events_pending() loop
finished = False

# This will load modules on a thread. A simple "import module" does not work
def do_import(module_name):
    thismodule = sys.modules[__name__]
    module = importlib.import_module(module_name)
    setattr(thismodule, module_name, module)
    print(module_name, 'imported')
    # Use the last module being imported so we know when to break from the Gtk.events_pending()
    if (module_name == "matplotlib.pyplot"):
        global finished
        finished = True

spin_plug.show_all()

modules_to_load = ['argparse', 'collections', 'csv', 'matplotlib', 'matplotlib.pyplot']

# Loop through and create a thread for each module to import from the list
for module_name in modules_to_load:
    thread = threading.Thread(target=do_import, args=(module_name,))
    thread.start()

# We cannot simply run an infinite Gtk.main() loop, so iterate until the plug has been filled
# Busy wait continues to allow the spinner to spin until the computer loads the modules. Since
# each computer will have a different loading speed, a busy wait of 300 should cover slower
# machines. We can break out of the loop early once the last module is loaded.
busy_wait = 0
while (Gtk.events_pending() or busy_wait < 300):
    #print("Busy Wait: %d" % busy_wait)
    #print ("finished: %d" % finished)
    if (finished):
        break
    Gtk.main_iteration()
    if (not Gtk.events_pending()):
        busy_wait = busy_wait + 1