9

我正在寻找一个很好的解释,说明在图形应用程序中使用多线程的必要性。在下面的示例中使用了 Python,但问题不是 Python 特定的,它可能适用于任何语言的图形编程的一般设计。

让我们举一个简单的例子。让我们假设有一个应用程序对一组文件执行某种耗时的操作,并将它的进度输出到控制台。让我们假设这个操作每个文件需要 2 秒,并且有 10 个文件需要处理,分别称为 1.txt、2.txt、3.txt、... 10.txt。然后一个示例实现可能如下所示:

安慰

import time
def process(file):
    print 'processing {0}...'.format(file)
    time.sleep(2.0) #simulate slow operation

files = ['{0}.txt'.format(i) for i in range(1, 11)]
map(process, files)

控制台示例当然是单线程的,并且可以很好地完成工作。现在,如果我们想添加图形进度条,单线程实现可能如下所示:

单线程图形界面

import time, gtk, gobject

def process(file):
    print 'processing {0}...'.format(file)
    time.sleep(2.0)

class MainWindow(gtk.Window):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.progress = gtk.ProgressBar()
        self.progress.set_fraction(0)
        self.add(self.progress)
        self.connect("destroy", gtk.main_quit)
        self.show_all()
        files = ['{0}.txt'.format(i) for i in range(1, 11)]
        gobject.timeout_add(100, self.submit, files, 0)

    def submit(self, files, i):
        process(files[i])
        self.progress.set_fraction((i + 1.0)/len(files))
        if i + 1 < len(files):
            gobject.idle_add(self.submit, files, i + 1)

win = MainWindow()
gtk.main()

这似乎工作正常,但是当您运行应用程序时,如果您尝试与应用程序交互(例如尝试调整窗口大小),它将卡住,并且只有在被释放以处理挂起的 gui 事件时每两秒响应一次。最后一个示例是多线程实现,并在整个执行过程中保持响应。

多线程图形用户界面

import time, gtk, gobject, threading

def process(file):
    print 'processing {0}...'.format(file)
    time.sleep(2.0)

class MainWindow(gtk.Window):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.progress = gtk.ProgressBar()
        self.progress.set_fraction(0)
        self.add(self.progress)
        self.connect("destroy", gtk.main_quit)
        self.show_all()
        files = ['{0}.txt'.format(i) for i in range(1, 11)]
        threading.Thread(target=self.submit, args=(files,)).start()

    def submit(self, files):
        for i, file in enumerate(files):
            process(file)
            gobject.idle_add(self.progress.set_fraction, (i + 1.0)/len(files))
            if not self.get_visible():
                return

gtk.gdk.threads_init()
win = MainWindow()
gtk.main()

对我来说,如果你的代码中有一个长时间运行的阻塞操作,并且你想要一个响应式的 gui,你必须使用多线程解决方案,这对我来说似乎非常清楚和合乎逻辑。没有其他方法可以解决它。是这样吗?我曾多次尝试向其他开发人员解释这一点,但许多人不理解或不同意。有人可以提供对这个概念的解释,关于它的文章的链接,或者如果我的理解不正确,请纠正我。

4

3 回答 3

8

你的理解是正确的。如果应用程序不是多线程的,则应用程序会等待每个操作完成。当您的应用程序是多线程的时,您使用一个线程来处理 GUI 操作,而另一个线程来处理文件。

我没有文章或类似内容的参考。如果您将线程视为人,可能会有所帮助,每个人都有自己的工作并且每个人一次只能做一件事。

于 2012-11-12T11:50:14.883 回答
3

主要原因是,GUI 工具包处理所有事件(鼠标移动、按钮单击、键盘输入、系统事件等)mainloop,这不是您为 GUI 应用程序编写的代码的一部分。

此主循环调用您提供的所有事件处理程序和其他函数。现在,如果其中一个函数花费的时间太长(例如 > 100 毫秒),则会对 UI 响应性产生非常明显的影响,因为主循环将无法处理更多事件。

无论使用哪种工具包的编程语言,都应该在任何有关 GUI 编程的书籍的“高级概念”部分中详细讨论这个主题。

于 2012-11-12T12:13:52.313 回答
0

多线程是解决这个问题的一种方法,但不一定是唯一(或最好)的方法。作为一个 python 示例,greenlets提供了一种在同一线程中运行并发进程的方法,避免了与多线程相关的锁定问题。在大多数情况下,我当然会认为 greenlets 是首选解决方案,因为它们相对容易编码。

于 2012-11-12T11:56:04.490 回答