从tkinter小部件中启动一个线程

时间:2015-01-15 17:47:14

标签: python multithreading python-3.x tkinter

首先,我不得不说我不是Python的专家。我从示例中收集了大部分代码。

我做了一些研究,并且无法找到类似于我想要做的代码配置:从自定义窗口小部件类中启动一个线程。我知道tkinter有多个线程尝试与单个小部件通信时出现问题,但我不会在这里看到这种情况。

我尝试这样做的原因是每个小部件都可以启动/停止并在其自身上进行更新。通过这种方式,我可以在同一个根窗口中查看来自多个源的数据,并且可以随意添加/删除每个源。我知道我可以用不同的方式编码(所有在一个类中),但我想以这种方式尝试。我认为这会让事情更清洁。

我的思考过程如下:

  1. 创建可重复使用的自定义窗口小部件类。
  2. 能够将多个小部件添加到根窗口(并且还能够删除它们)
  3. 每个小部件都将获取数据,并独立于其他小部件进行更新。
  4. 使用线程,以便每个小部件都可以在后台执行所需的操作。这样,所有小部件都会同时更新。
  5. 我的代码示例显示了我要完成的基础知识。在完成的程序中,每个小部件都有一个文本框,用于定义从何处获取数据。

    此测试代码在WindowsXP中使用Python3正常工作。每个小部件都可以添加和删除,并且可以独立于其他小部件进行自我更新。

    当我在Fedora20中使用Python3运行相同的代码时,它会在按下启动按钮时崩溃。例外情况如下:

    Exception in thread Thread-1:
    Traceback (most recent call last):
    File "/usr/lib/python3.3/threading.py", line 637, in _bootstrap_inner
      self.run()
    File "/usr/lib/python3.3/threading.py", line 594, in run
      self._target(*self._args, **self._kwargs)
    File "dummy.py", line 44, in updlabel
      self.label.config(text=number)
    File "/usr/lib/python3.3/tkinter/__init__.py", line 1263, in configure
      return self._configure('configure', cnf, kw)
    File "/usr/lib/python3.3/tkinter/__init__.py", line 1254, in _configure
      self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
    _tkinter.TclError: None
    

    所以最终,我有两个问题:

    1. 为什么它不能在Linux中运行,而在WindowsXP中呢?
    2. 我的方法是否有效?如果是,我在此代码中的问题在哪里?
    3. 代码示例:

      from threading import Thread
      import tkinter as tk
      import numpy as np
      import time
      
      class Widget(tk.Frame):
          def __init__(self, master):
              tk.Frame.__init__(self, master)
      
              self.running = False
              self.abort = True
      
              labelfont = ('times', 20, 'bold')
      
              self.label = tk.Label(self, text="---", font=labelfont)
              self.startb = tk.Button(self, text="START", command=lambda: self.sbpressed())
              self.remove = tk.Button(self, text="-", command=lambda: self.rbpressed())
      
              self.startb.grid(row=0, column=0)
              self.label.grid(row=0, column=1)
              self.remove.grid(row=0, column=2)
      
          def rbpressed(self):
              self.abort = True
              while self.running:
                  self.update()
              self.destroy()
      
          def sbpressed(self):
              if self.running:
                  self.abort = True
                  self.update()
              else:
                  self.startb["text"] = "ABORT"
                  self.running = True
                  self.abort = False
                  self.update()
                  self.t = Thread(target=self.updlabel, args=())
                  self.t.start()
      
          def updlabel(self):
              while self.abort == False:
                  number = str(np.random.random_integers(100))
                  self.label.config(text=number)
                  time.sleep(1)
              self.startb["text"] = "START"
              self.running = False
              self.abort = False
      
      class Application(tk.Frame):
          def __init__(self, master=None):
              tk.Frame.__init__(self, master)
              self.pack()
              self.addb = tk.Button(self, text="+", command=lambda: self.addwidget())
              self.addb.pack()
              Widget(self).pack()
      
          def addwidget(self):
              Widget(self).pack()
      
      root = tk.Tk()
      app = Application(master=root)
      app.mainloop()
      

1 个答案:

答案 0 :(得分:2)

我认为你的基本设计存在缺陷。正如您所提到的,tkinter并未设计为在多个线程中工作。永远不应该在创建根窗口的线程之外的线程中调用tkinter函数。这几乎肯定是你问题的根源。 可能工作,或者可能不工作 - 这是非线程安全的本质。

另外,作为一般规则,你不应该打电话给update - 它比你意识到的要多,而且通常是完全没必要的。

普遍接受的解决方案是您的线程需要将信息放入队列,并且您的主线程可以从队列中提取数据并对其进行操作。例如,您可以放置​​由窗口小部件和应显示的字符串组成的元组。然后,您的主程序可以轮询队列,关闭项目并使用新文本配置小部件。