【问题标题】:Unable to initialize a window and wait for a process to end in Python 3 + GTK+ 3无法初始化窗口并等待进程在 Python 3 + GTK+ 3 中结束
【发布时间】:2016-05-04 07:57:10
【问题描述】:

我是面向对象编程、Python 和 GTK+3 的新手,但我对过程编程(主要是 C)有相当的了解。

我正在尝试构建一个简单的 Python + GTK+ 3 脚本以在 Linux 下运行 pkexec apt-get update

我有一个mainWindow 类(基于Gtk.Window 类),其中包含一个名为button 的按钮对象(基于Gtk.Button 类),它触发mainWindow 中定义的new_update_window() 方法clicked 事件;

new_update_window() 方法从包含名为 label 的标签对象(基于 Gtk.Label 类)的 updateWindow 类(基于 Gtk.Window 类)初始化 updateWindow 对象并调用方法show_all()update()定义在updateWindow中;

update() 方法应该更改 label,运行 pkexec apt-get update 并再次更改 label

问题是无论我做什么都会出现以下情况之一:

  • 如果我直接运行subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"]),会显示update.Window,但会立即将label 设置为只有在pkexec apt-get update 完成执行后才应设置的值;
  • 如果我直接运行subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"]),直到pkexec apt-get update 执行完毕,update.Window 才会显示;
  • 我尝试了importing threading,在updateWindow 中定义了一个单独的run_update() 方法并使用thread = threading.Thread(target=self.run_update)thread.start()thread.join() 在单独的线程中启动该函数,但仍然取决于哪个我在run_update()subprocess.call()subprocess.Popen)中调用的方法显示了上述相关行为。

Tl;博士

我不知道如何完成我所追求的,即:

  1. 显示updateWindow (Gtk.Window)
  2. updateWindow 中更新label (Gtk.Label)
  3. 正在运行pkexec apt-get update
  4. updateWindow 中更新label
  • subprocess.Popen()update.Window 已显示,但 label 立即设置为只有在 pkexec apt-get update 完成执行后才应设置的值;
  • subprocess.call()update.Window 直到 pkexec apt-get update 完成执行后才会显示;
  • 将两者中的任何一个包装在函数中并在单独的线程中运行该函数不会改变任何内容。

这是代码;

不使用线程(案例1,在本例中使用subprocess.Popen()):

#!/usr/bin/python3
from gi.repository import Gtk
import subprocess

class mainWindow(Gtk.Window):

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

        button = Gtk.Button()
        button.set_label("Update")
        button.connect("clicked", self.new_update_window)
        self.add(button)

    def new_update_window(self, button):
        update = updateWindow()
        update.show_all()
        update.update()

class updateWindow(Gtk.Window):

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

        self.label = Gtk.Label()
        self.label.set_text("Idling...")
        self.add(self.label)

    def update(self):
        self.label.set_text("Updating... Please wait.")
        subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
        self.label.set_text("Updated.")

    def run_update(self):

main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()

使用线程(案例3,在本例中使用subprocess.Popen()):

#!/usr/bin/python3
from gi.repository import Gtk
import threading
import subprocess

class mainWindow(Gtk.Window):

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

        button = Gtk.Button()
        button.set_label("Update")
        button.connect("clicked", self.new_update_window)
        self.add(button)

    def new_update_window(self, button):
        update = updateWindow()
        update.show_all()
        update.update()

class updateWindow(Gtk.Window):

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

        self.label = Gtk.Label()
        self.label.set_text("Idling...")
        self.add(self.label)

    def update(self):
        self.label.set_text("Updating... Please wait.")
        thread = threading.Thread(target=self.run_update)
        thread.start()
        thread.join()
        self.label.set_text("Updated.")

    def run_update(self):
        subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])

main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()

【问题讨论】:

  • thread.join() 将等到run_update 完成,这可能不是故意的。
  • @J.J.Hakala 更新,使其更有意义。 thread.join() 的重点是在触发 self.label.set_text("Updated.") 之前等待 run_update() 完成,但实际发生的是它等待 run_update() 完成并继续执行 @ 987654384@(即:当pkexec apt-get update 仍在运行时,我可以看到标签报告“已更新。”)。

标签: linux python-3.x subprocess gtk3


【解决方案1】:

您可以使用与 GTK 的主循环集成的Gio.Subprocess,而不是使用 Python 的 subprocess 模块:

#!/usr/bin/python3
from gi.repository import Gtk, Gio

# ...

class updateWindow(Gtk.Window):

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

        self.label = Gtk.Label()
        self.label.set_text("Idling...")
        self.add(self.label)

    def update(self):
        self.label.set_text("Updating... Please wait.")
        subprocess = Gio.Subprocess.new(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"], 0)
        subprocess.wait_check_async(None, self._on_update_finished)

    def _on_update_finished(self, subprocess, result):
        subprocess.wait_check_finish(result)
        self.label.set_text("Updated.")

【讨论】:

  • 这似乎是“正确”的做法,谢谢。我会发送一个编辑,但它太短了,我不想无用地混淆答案:Gio.Subprocess.new() method 预计在argv 之后至少有一个Gio.SubprocessFlags,所以需要一个来制作 sn -p 工作:Gio.Subprocess.new(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"], 0).
【解决方案2】:

你快到了...
并且解决方案非常简单:)

您遇到的问题是 subprocess.call() 会冻结 GUI(循环),从而阻止窗口出现,而 subprocess.Popen() 会抛出命令并跳转到 self.label.set_text("Updated.")

如何解决

你可以简单地通过运行一个单独的线程,调用你的命令来解决它:

subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])

并移动标签更改命令

self.label.set_text("Updated.")

进入线程,定位在第一个命令之后。然后线程不会冻结接口,而label 不会过早更改,因为subprocess.call() 会阻止这种情况。

然后代码变成:

#!/usr/bin/python3
from gi.repository import Gtk
from threading import Thread
import subprocess

class mainWindow(Gtk.Window):

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

        button = Gtk.Button()
        button.set_label("Update")
        button.connect("clicked", self.new_update_window)
        self.add(button)

    def new_update_window(self, button):
        update = updateWindow()
        update.show_all()
        update.update()

class updateWindow(Gtk.Window):

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

        self.label = Gtk.Label()
        self.label.set_text("Idling...")
        self.add(self.label)

    def update(self):
        self.label.set_text("Updating... Please wait.")
        Thread(target = self.run_update).start()

    def run_update(self):
        subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
        self.label.set_text("Updated.")

main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()

或者

如果您想避免使用Thread,可以使用Gtk.main_iteration() 来防止界面在进程运行时冻结:

#!/usr/bin/python3
from gi.repository import Gtk
import subprocess

class mainWindow(Gtk.Window):

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

        button = Gtk.Button()
        button.set_label("Update")
        button.connect("clicked", self.new_update_window)
        self.add(button)

    def new_update_window(self, button):
        update = updateWindow()
        update.show_all()
        update.update()

class updateWindow(Gtk.Window):

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

        self.label = Gtk.Label()
        self.label.set_text("Idling...")
        self.add(self.label)

    def update(self):
        self.label.set_text("Updating... Please wait.")
        subprocess.Popen(["gedit"])
        self.hold()
        self.label.set_text("Updated.")

    def run_update(self):
        subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])

    def hold(self):
        while True:
            Gtk.main_iteration()
            try:
                subprocess.check_output(["pgrep", "apt-get"]).decode("utf-8")
            except subprocess.CalledProcessError:
                break

main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()

编辑

不断深入了解有一种更好的使用线程的方法,然后在我的回答中发布。

您可以在Gtk GUI 中使用线程,使用

GObject.threads_init() 

然后,要更新接口线程,使用

GObject.idle_add()

来自this (slgihtly outdated) link

...在应用程序初始化时调用 gobject.threads_init()。然后你正常启动你的线程,但确保线程从不直接执行任何 GUI 任务。相反,您使用 gobject.idle_add 来安排 GUI 任务在主线程中执行

当我们将gobject.threads_init() 替换为GObject.threads_init() 并将gobject.idle_add 替换为GObject.idle_add() 时,我们几乎有了如何在Gtk 应用程序中运行线程的更新版本。

在您的代码中应用(使用第二个示例,使用线程):

#!/usr/bin/python3
from gi.repository import Gtk, GObject
from threading import Thread
import subprocess

class mainWindow(Gtk.Window):

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

        button = Gtk.Button()
        button.set_label("Update")
        button.connect("clicked", self.new_update_window)
        self.add(button)

    def new_update_window(self, button):
        update = updateWindow()
        update.show_all()
        update.update()

class updateWindow(Gtk.Window):

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

        self.label = Gtk.Label()
        self.label.set_text("Idling...")
        self.add(self.label)

    def update(self):
        # self.thread = threading.Thread(target=self.run_update)
        thread = Thread(target=self.run_update)
        thread.start()

    def run_update(self):
        GObject.idle_add(
            self.label.set_text, "Updating... Please wait.",
            priority=GObject.PRIORITY_DEFAULT
            )
        subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
        GObject.idle_add(
            self.label.set_text, "Updated.",
            priority=GObject.PRIORITY_DEFAULT
            )

GObject.threads_init()
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()

【讨论】:

  • 不建议您使用线程的第一个解决方案,因为您不应该从主线程以外的线程修改 GUI。
【解决方案3】:

def run_update(self):
    subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])

您不是在等待进程终止,请尝试

def run_update(self):
    proc = subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
    proc.wait()

相反。这应该正确等待完成,但它不会有太大帮助,因为将从 mainWindow.new_update_window 调用 updateWindow.update 并且 GUI 线程将等待该过程完成。

subprocess.Popensubprocess.call启动的进程完成时,可以使用自定义信号进行通信:

#!/usr/bin/python3

from gi.repository import Gtk, GObject
import threading
import subprocess

class mainWindow(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title = "Updater")

        button = Gtk.Button()
        button.set_label("Update")
        button.connect("clicked", self.new_update_window)
        self.add(button)

    def new_update_window(self, button):
        update = updateWindow(self)
        update.show_all()
        update.start_update()

class updateWindow(Gtk.Window):
    def __init__(self, parent):
        Gtk.Window.__init__(self, title = "Updating...")

        self.label = Gtk.Label()
        self.label.set_text("Idling...")
        self.add(self.label)
        self.parent = parent

        GObject.signal_new('update_complete', self, GObject.SIGNAL_RUN_LAST,
                           None, (int,))
        self.connect('update_complete', self.on_update_complete)

    def on_update_complete(self, widget, rc):
        self.label.set_text("Updated {:d}".format(rc))
        # emit a signal to mainwindow if needed, self.parent.emit(...)

    def start_update(self):
        self.label.set_text("Updating... Please wait.")
        thread = threading.Thread(target=self.run_update)
        thread.start()

    def run_update(self):
        rc = subprocess.call(["/usr/bin/pkexec", "apt-get", "update"],
                                shell=False)
        self.emit('update_complete', rc)

main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()

【讨论】:

  • 似乎没有帮助。我试过了,现在没有显示窗口,pkexec 的提示在输入密码后仍停留在屏幕上,只有在手动关闭它时才会显示窗口,但和之前一样,即使apt-get 已经更改了标签仍在运行。这既可以直接调用函数,也可以像第二个示例一样让函数在新线程中调用。 subprocess.Popen() 似乎让执行继续进行,就像subprocess.call() 似乎阻止它一样。
  • 没有看到您对答案的更新,谢谢,这行得通。
猜你喜欢
  • 1970-01-01
  • 2013-05-15
  • 1970-01-01
  • 1970-01-01
  • 2013-12-03
  • 1970-01-01
  • 1970-01-01
  • 2017-09-12
  • 1970-01-01
相关资源
最近更新 更多