2

MyApp クラスで定義されたテキスト フィールドにアクセスして、MyThread class def run(self): somethin から 'step 2' を書き込みたい :

self.text.insert(1.0,"ステップ 2")

コード:

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明確に定義された場所に書き込む静的オブジェクトを取得しただけなので、これが簡単になります。動的に変化する場合は、イベントをバインドするか、イベントをアタッチする必要がStringVarありtraceますが、ここでは簡単にしましょう。

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.runapp.text


また、コードにスレッドを使用する正当な理由がおそらくないことにも注意してください。

コードを約 5 秒で実行したい場合は、5 秒間スリープするスレッドを作成する代わりに、約 5 秒で実行するように Tkinter に依頼してください。

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 秒後に実行したい処理に時間がかかる場合や、ブロックする必要がある場合など、これは機能しません。しかし、それがあなたのコードに当てはまるとは思えません。


コードの新しいバージョンでは、次の 1 つの点を除いて、ほぼすべてが正しくなりました。

MyThread.startの代わりにバックグラウンド スレッド関数を入れますMyThread.run

ドキュメントが言うように:

サブクラスで他のメソッド (コンストラクターを除く) をオーバーライドする必要はありません。つまり、このクラスのメソッド__init__()とメソッドのみをオーバーライドします。run()

デフォルトのstartメソッドは、新しいスレッドを開始し、その新しいスレッドを実行する関数ですself.run()。したがって、 をオーバーライドself.runすると、そこに置いたものはすべてバックグラウンド スレッドで実行されます。しかし、 をオーバーライドstartすると、新しいスレッドを作成する代わりに、そこで実装したことは何でも実行されます。これは、あなたの場合はブロッキング関数です。

したがって、名前を に変更startするだけrunで、すべてが機能します。


余談ですが、誤ってメイン スレッドをブロックしてしまったかどうかを確認するのに役立つことの 1 つは、隅に小さな時計を追加することです。たとえば、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 に答える