2

我想访问 MyApp 类中定义的文本字段,以从 MyThread 类 def run(self) 中写入“步骤 2”:类似这样的东西:

self.text.insert(1.0,"第二步")

代码:

import threading
import time
from Tkinter import *


class MyThread(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        time.sleep(5)
        #i want to access the text here

class MyApp(Frame):


    def __init__(self, master):
        Frame.__init__(self, master)

        self.my_widgets()

    def my_widgets(self):
        self.grid()

        self.my_button = Button(self, text="Start my function",
                                          command=self.my_function)
        self.my_button.grid(row=0, column=0)

        self.text = Text(self, width = 60, height = 5, wrap = WORD)
        self.text.grid(row = 10, column = 0, columnspan = 2, sticky = W)

    def my_function(self):
        self.text.insert(1.0,"Step one") 

        mt = MyThread()
        mt.start()


root = Tk()
root.title("Client")
root.geometry("500x500")
app = MyApp(root)

root.mainloop()

我对线程不是很熟悉,所以任何帮助都将不胜感激

@abarnet 使用您的答案,我这样做了,但是 GUI 在等待连接时没有响应

from Tkinter import *
from mtTkinter import *
import socket
import sys


class Application(Frame):

    def __init__(self, master):

        Frame.__init__(self, master)
        self.grid()
        self.create_widgets()

    def create_widgets(self):

        self.submit_button = Button(self, text='start', command = self.my_function)
        self.submit_button.grid(row = 2, column = 0, sticky = W)

        self.text = Text(self, width = 60, height = 5, wrap = WORD)
        self.text.grid(row = 10, column = 0, columnspan = 2, sticky = W)

    def my_function(self):

        mt = MyThread()
        mt.start()


class MyThread(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)


    def start(self):
        app.text.insert(6.0,'server started')
        app.text.update_idletasks()

        app.text.insert(6.0,'\n'+'waiting for client')

        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.bind(('',1090))
        self.s.listen(1)



        self.sc, address = self.s.accept()
        address = str(address)

        self.instruction = Label(self, text = 'got connection from '+address)
        self.instruction.grid(row=7, column = 0, columnspan = 2, sticky = W)
        self.instruction.update_idletasks()

        msg = self.sc.recv(1024)
        s=msg.find('.')
        g=msg[s:]
        i=1
        f = open('file_'+ str(i)+g,'wb') #open in binary
        i=i+1
        while (True):       
            l = self.sc.recv(1024)

            while (l):

                f.write(l) 
                f.flush()
                l = self.sc.recv(1024)
        f.close()    

        self.sc.close()

        self.s.close()


root = Tk()
root.title("Server")
root.geometry("500x250")
app = Application(root)

root.mainloop()
4

1 回答 1

2

Tkinter 不是线程安全的。这意味着您不能从另一个线程访问 Tkinter 对象。

有多种方法可以解决这个问题,但我认为最简单的方法是创建一个普通的旧字符串,受锁保护,并Text在它发生变化时将其内容复制到该变量中。

在您的代码中,您刚刚Text在定义明确的位置写入了一个静态对象,这使得这很容易。如果它是动态变化的,你要么想绑定一个事件,要么附加一个StringVartrace它,但让我们在这里保持简单。

class MyThread(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        time.sleep(5)
        with app.text_lock:
            text_value = app.text_value

class MyApp(Frame):
    def __init__(self, master):
        Frame.__init__(self, master)
        self.text_value = ''
        self.text_lock = threading.Lock()
        self.my_widgets()

    def my_widgets(self):
        # ...

    def my_function(self):
        self.text.insert(1.0,"Step one")

        with self.text_lock:
            self.text_value = "Step one" + self.text_value
        # ...

# ...

另一种选择是使用mtTkinter,它通过拦截所有 GUI 方法并将它们传递给队列,为您提供线程安全的 Tkinter。如果你不明白这意味着什么,那就是魔法。您的MyThread.run方法可以像访问app.text主线程中的代码一样访问。


此外,值得注意的是,您的代码可能没有任何充分的理由使用线程。

如果您希望某些代码在大约 5 秒内运行,而不是创建一个线程休眠 5 秒,只需让 Tkinter 在大约 5 秒内运行它:

class MyApp(Frame):
    # ...

    def my_function(self):
        self.text.insert(1.0,"Step one") 

        self.after(5000, self.my_thing_to_do_later)

    def my_thing_to_do_later(self):
        # same code you would put in MyThread.run, but now
        # you're on the main thread, so you can just access 
        # self.text directly

当然,与任何其他事件处理程序一样,如果您想要在 5 秒后执行的操作需要很长时间或需要阻塞或其他任何事情,这将不起作用。但我怀疑你的代码就是这种情况。


在新版本的代码中,您几乎做对了所有事情,除了一件事:

您将后台线程函数放入MyThread.start而不是MyThread.run.

正如文档所说:

不应在子类中覆盖任何其他方法(构造函数除外)。也就是说,只覆盖这个类的__init__()run()方法。

默认start方法是启动一个新线程的函数,然后该新线程执行self.run()。所以,如果你 override self.run,无论你放在那里的东西都会在后台线程中运行。但是,如果您 override start,而不是创建一个新线程,它会执行您在那里实现的任何操作 - 在您的情况下,它是一个阻塞函数。

因此,只需重命名startrun,一切正常。


作为旁注,可以帮助我查看是否不小心阻塞了主线程的一件事是在角落添加一个小时钟。例如,在 中App,添加此方法:

def update_clock(self):
    self.clock.configure(text=time.strftime('%H:%M:%S'))
    self.after(1000, self.update_clock)

然后,在 末尾create_widgets,添加以下行:

self.clock = Label(self)
self.clock.grid(row=2, column=1)
self.update_clock()
于 2013-06-28T00:25:18.827 回答