2017-05-11 15 views
0

いくつかのボタンをクリックしたときに時間がかかる操作を行うtkinter GUIを作成しました。私のケースには2つの大きな問題があります。私は、tkinterが一貫性のない方法でクラッシュする原因と考えると思います。新しいスレッドでTkinterのpythonがクラッシュしてメインスレッドにログオンしようとしました

1)新しいスレッドの出力を、メインスレッド上で実行されるスクロールされたテキストウィジェットに記録したいと思っています。これは良い考えではありません。今すぐ私はそれを行うTextHandlerオブジェクトをスレッドに送信する(私のコードを参照してください)

2)新しいスレッドからメインスレッドへのロギングを避けるために、私はスレッドが完了し、メインスレッドからテキストウィジェットにログを記録しますが、ボタンが押されたときに呼び出される関数でスレッドが作成されると、メインループはどのように認識されますか?

class TextHandler(logging.Handler): 

"""This class allows you to log to a Tkinter Text or ScrolledText widget""" 

def __init__(self, text): 
    # run the regular Handler __init__ 
    logging.Handler.__init__(self) 
    # Store a reference to the Text it will log to 
    self.text = text 

def emit(self, record): 
    msg = self.format(record) 
    def append(): 

     self.text.configure(state='normal') 

     self.text.insert(tkinter.END, msg + '\n') 
     self.text.configure(state=DISABLED) 
     # Autoscroll to the bottom 
     self.text.yview(tkinter.END) 
    # This is necessary because we can't modify the Text from other threads 
    self.text.after(0, append) 


class GUI(Frame): 

def __init__(self, parent, logger, st): 
....initialize stuff... 


def _startComputations(self): 

    if (self._paths == ""): 
     self._logger.warn("No drive paths found, please add a path") 
    else: 
     if (self._flexinput == ""): 
      self._logger.warn(self._flexinput) 
      self._logger.warn("No file path,continuing with standard KM estimation") 

     self.myThread = StandardUsecases(self._paths, self._input, 
            self._logger, self._standard_choices, 
            self._numberOfoccurences_usecases, 
            self._all_logs, self._unique_countries, 
            self.bt, self.bt2, self.bt3, 
            self._flexinput) 
     self.myThread.start() 


     self._all_jsons = self.myThread._original_json_list 
     self._general_json = self.myThread._general_json_data_list 


def _KM_Button(self): 

    self._all_logs[:] = [] 
    self.bt = Button(self.frame6, text='1-Main Usecases', font = "Helvetica 11 bold italic", 
       command = self._startComputations,relief = RAISED, bd= 6, bg = "pale green", fg = 'black') 
    self.bt.pack(side = LEFT) 

def initGUI(self): 

    self.parent.title("DC Statistics calculator") 

    self.pack(fill=BOTH, expand=True) 
    self._Drive_Paths() 
    self._Flexray_Path() 
    #self._UserInput() 
    self._KM_Button() 
    self._countButton() 
    self._exportButton() 
    self._helpButton() 

def main(): 

root = Tk() 
root.geometry("700x800+400+400") 

st = scrolledtext.ScrolledText(root, state='disabled') 
st.configure(font="Times 13") 
st.pack(side=BOTTOM, fill='both',expand='yes') 

text_handler = TextHandler(st) 
logger = logging.getLogger() 
logger.addHandler(text_handler) 

app = GUI(root, logger, st) 
root.mainloop() 

if __name__ == '__main__': 
    main() 

ボタンを押すと_startComputations関数が呼び出され、スレッドが作成されます。 _loggerオブジェクトを送信して、私が新しいスレッドにいる間にScrolledTextウィジェットにログすることができます。しかし、ほとんどの場合、「python.exeは動作を停止しました」または共有オブジェクトで呼び出されたtcl_appendlimitedtoobjのようなクラッシュを受けます。

新しいスレッドからログを記録したくない場合は、ボタンを押した後に呼び出される関数で新しいスレッドが作成されるため、メインループで新しいスレッドが完了したかどうかを知ることができますか?あなたが別のスレッドにTextHandlerのインスタンスと一緒にあなたのtextウィジェットを渡すので、あなたのエラーがoccures - 私がコメントで言ったように

はあなたに

+0

あなたのコードはスレッドセーフではないと思います。これは、テキストウィジェットを 'TextHandler'のインスタンスと一緒に別のスレッドに渡すためです。 'TextHandler'のいくつかのメソッドは' text'ウィジェット(例えば 'emit')のメソッドをキャストし、メインスレッドからキャストする必要があります。あなたのコードは[MCVE](http://stackoverflow.com/help/mcve)ではないので、あなたが修正する必要があることを伝えるのは難しいですが、もっと簡単なオプションがあります。ロギング目的で 'stdout'、ウィジェットやファイルに' print'を直接ハイジャックすることもできます! – CommonSense

+0

「ハイジャック」の部分について説明できますか?また、方法はありますか?メインスレッドは新しいスレッドが完了したことを知りますか?私はそれを見るので、tkinter mainloopは新しいスレッドについて「知っていません」ので、スレッド以外の場所でis_alive()メソッドを呼び出すことはできません – Dimitrios

+0

別のアイデア:あなたは "コミット" ['Queue'](https://docs.python.org/3/library/asyncio-queue.html)(これはスレッドセーフではありません)オブジェクトに/から送出されます。 ['after'](http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget)にチェックすることもできます(スレッドの' is_alive() 'ステータスをチェックしてチェックすることもできます)。 after-method) "loop"(関数はこのメソッドのおかげで自身を再スケジュールできます)。 "ハイジャック"部分の一般的な[アイデア](http://stackoverflow.com/questions/3333334/stdout-to-tkinter-gui)([example](http://stackoverflow.com/a/43805139/6634373)) - 'stdout'ものをリダイレクトします。 – CommonSense

答えて

0

ありがとうございます。 TextHandlerの一部のメソッドは、textウィジェットのメソッドをキャストします(例:emit)。メインスレッドからキャストする必要があります。

問題を解決する最も簡単な解決策は、queueオブジェクトです。他のスレッドからtkinterメソッドを(例えば、何かをウィジェットに書き込む)キャストすることはできませんが、queueを埋め込むことができます!ここでの主なアイデアは、queueを確認しながら、タスクのスレッドの誰かが存在することです。あなたが聞くために開始することができます。もちろん、

def listen_queue(self): 
    # listen queue 
    while self.log_queue.qsize(): 
     try: 
      self.logger.warning(self.log_queue.get()) 
     except queue.Empty: 
      pass 

:我々はpullqueueから何かをしようとする人の中に

def listen(self, force_start=False): 
    # "after" loop - listener 
    self.listen_queue() 

    if self.task_list or force_start: 
     print('Listener: Listen') 
     self.after(100, self.listen) 
    else: 
     print('Listener: Off') 

:continiously我々はafter「ループ」とタスクのlistを必要とするqueueを確認するにはqueueあなたのアプリケーションの開始時から、ループは次のようになります:

しかし、スレッドをより詳細に制御したい場合は、listを使用することをお勧めします。私は今あなたがパターンを持っていると思いますので、ここに完全な例があります:

# imports 
try: 
    import tkinter as tk    # Python 3 
    import queue 
except ImportError: 
    import Tkinter as tk    # Python 2 
    import Queue as queue 

import logging 
import threading 
import random 
import string 


# classes 
class TextHandler(logging.Handler): 
    def __init__(self, text): 
     # run the regular Handler __init__ 
     logging.Handler.__init__(self) 
     # Store a reference to the Text it will log to 
     self.text = text 

    def emit(self, record): 
     msg = self.format(record) 

     self.text.configure(state='normal') 
     self.text.insert(tk.END, msg + '\n') 
     self.text.configure(state=tk.DISABLED) 
     self.text.yview(tk.END) 


class App(tk.Tk): 
    # common tk app 
    def __init__(self): 
     # initiate root 
     tk.Tk.__init__(self) 
     self.resizable(width=False, height=False) 

     # initiate widgets 
     self.spawn_task_button = tk.Button(self, text='Spawn Task', command=self.spawn_task) 
     self.spawn_task_button.pack(expand=True, fill='x') 

     self.quit_button = tk.Button(self, text='Quit', command=self.close_app) 
     self.quit_button.pack(expand=True, fill='x') 

     self.text_console = tk.Text(self, bg='black', fg='white') 
     self.text_console.pack(expand=True, fill='both') 

     # initiate queue, task list, logger 
     self.logger = logging.getLogger() 
     self.logger.addHandler(TextHandler(self.text_console)) 

     self.log_queue = queue.Queue() 
     self.task_list = [] 

     # initiate events and protocols 
     self.protocol('WM_DELETE_WINDOW', self.close_app) 

    def put_line_to_queue(self, log_line=''): 
     # put log line to queue 
     self.log_queue.put(log_line) 

    def listen_queue(self): 
     # listen queue 
     while self.log_queue.qsize(): 
      try: 
       self.logger.warning(self.log_queue.get()) 
      except queue.Empty: 
       pass 

    def listen(self, force_start=False): 
     # "after" loop - listener 
     self.listen_queue() 

     if self.task_list or force_start: 
      print('Listener: Listen') 
      self.after(100, self.listen) 
     else: 
      print('Listener: Off') 

    def common_task(self, task_thread): 
     # example task wait + print 

     # add task to task_list 
     self.task_list.append(task_thread) 

     iteration_count = random.randint(1, 10) 
     task_numb = task_thread.name[-1:] 

     self.put_line_to_queue('\n*** Task %s: Spawned \t Iteration count: %d ***\n' % (task_numb, iteration_count)) 

     for _ in range(iteration_count): 
      threading.Event().wait(1) 
      self.put_line_to_queue('Task %s: In Progress \t Iteration: %d \t Generated: %s' % (task_numb, _ + 1, 
                           generate_smth())) 

     self.put_line_to_queue('Task %s: Completed\n' % task_numb) 

     # remove task from task_list 
     self.task_list.remove(task_thread) 

    def spawn_task(self): 
     # spawn another task 
     task = threading.Thread(target=lambda: self.common_task(task)) 

     # "kick start" listener if task list is empty 
     if not self.task_list: 
      self.listen(force_start=True) 

     task.start() 

    def close_app(self): 
     # handle closing 
     if self.task_list: 
      # code to handle threads 
      # there're some threads in a list 
      self.put_line_to_queue('\n**** Cant quit right now! ****\n') 
     else: 
      self.destroy() 


# functions 
def generate_smth(size=6, chars=string.ascii_uppercase + string.digits): 
    # generate random 
    return ''.join(random.choice(chars) for _ in range(size)) 

# entry point 
app = App() 
app.mainloop() 
関連する問題