質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

3回答

18433閲覧

Python3のtkinterで並行処理?を行いたい

tomo1998

総合スコア34

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

3クリップ

投稿2018/09/18 21:47

tkinterで、スタートボタンとストップボタンを生成し、スタートボタンが押されれば数字をカウントし始め、ストップボタンが押されればカウントをストップ といったプログラムを作ろうと思っています。

ースタートボタンを押したらスタートメソッドを呼び出してメソッド内のwhile文でカウントが始まり、ストップフラグが立っていないかif文で確認し、フラグが立っていればwhile文をbreakで抜ける。
ーストップボタンを押したらストップフラグが立つ。

というように作ろうとしたのですが、while文が一度反応すればwhile文による無限ループから離脱できなくなり、ボタンも固まりプログラムがフリーズしてしまいます。

発生している問題・エラーメッセージ

・無限ループから抜け出せずプログラムがフリーズする

該当のソースコード

python3

import tkinter as tk import threading def start(): global frame global stop while True: if stop==False: break else: print(frame) frame=frame+1 def stop(): global stop stop=False ######ここからはメソッド外 frame=1 stop=True root=tk.Tk() Button001=tk.Button(root,text="Start",command=start) Button001.pack() Button002=tk.Button(root,text="End",command=stop) Button002.pack() root.mainloop()

試したこと

ネットで調べたら、threadingを使えば並行処理ができると聞いたので、試した結果プログラムはフリーズしなくなり、ボタンも押せるようになったのですがどういったわけかストップボタンを押してストップフラグを立たせてもループが止まりません・・・

threadingを使った場合のコード(『ここからはメソッド外』以降から

frame=1 stop=True thread_1 = threading.Thread(target=start) thread_2 = threading.Thread(target=stop) root=tk.Tk() Button001=tk.Button(root,text="Start",command=thread_1.start()) Button001.pack() Button002=tk.Button(root,text="End",command=thread_2.start()) Button002.pack() root.mainloop()

もしかしたら簡単すぎるミスがどこかにあるかもしれないのですが、自分でいろいろ試してもわかりませんでした・・・
どなたかご回答お願いします。m(_ _)m
あとわかりにくかったらすいません

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

まずは動作しない原因を2点ほど

1.
tk.Button()command引数に渡すのは関数オブジェクトとなりますので、

Python

1Button001=tk.Button(root,text="Start",command=thread_1.start()) 2Button001.pack() 3Button002=tk.Button(root,text="End",command=thread_2.start()) 4Button002.pack()

の部分は

Python

1Button001=tk.Button(root,text="Start",command=thread_1.start) 2Button001.pack() 3Button002=tk.Button(root,text="End",command=thread_2.start) 4Button002.pack()

の間違いかと思います。

上の間違った記述だと、thread_*.start()実行 して、その結果を command に渡すという意味になってしまいます。

2.
stop という名前が 関数名 def stop() とフラグ stop=True の2つの使用している為、stop() が動作しておりませんので、どちらかの名前を変更してください。

とりあえず、フラグ名を stop_flag に変更するなどで良いかと思います。

上記の変更を行うことで、**『とりあえず』**は動作するかと思います。


ただし・・。上記の処理では
・threadを停止した後に threading.join()による リソースの回収処理部がない
・threadの開始処理(start())は1度だけしかできないので、2度目の開始ができない
・アプリケーションが終了したときのthread停止処理が入ってない

等などの問題があります。

個人的には
・現状のstart()stop()処理をそのままスレッドで実行する

のではなく、

・スレッド処理部は start()stop()の関数以外で行う
start() にてスレッドの生成やstart()処理を行う
stop() にてスレッドの停止やjoin()処理を行う

といった処理にするほうが良いかと思います。

以下は軽くサンプルです。

Python

1import tkinter as tk 2import threading 3import time 4 5def time_count(): 6 global frame 7 global stop_flag 8 9 while not stop_flag: 10 print(frame) 11 frame=frame+1 12 time.sleep(1) 13 14def start(): 15 global stop_flag 16 global thread 17 18 # スレッドが無いなら生成してstart()する 19 if not thread: 20 thread = threading.Thread(target=time_count) 21 stop_flag=False 22 thread.start() 23 24def stop(): 25 global stop_flag 26 global thread 27 28 # スレッドがある場合停止してjoin()する 29 if thread: 30 stop_flag=True 31 thread.join() 32 thread=None 33 34frame=1 35stop_flag=False 36thread=None 37root=tk.Tk() 38Button001=tk.Button(root,text="Start",command=start) 39Button001.pack() 40Button002=tk.Button(root,text="Stop",command=stop) 41Button002.pack() 42root.mainloop() 43# 終了時にスレッドを停止する処理 44stop_flag=True 45thread.join()

投稿2018/09/19 00:16

magichan

総合スコア15898

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tomo1998

2018/09/19 14:41

commandのところは完全にうっかりミスでした・・・w つい関数宣言と同じ感覚で・・・w ()ついたままだと、結果をcommandに渡す、ということになるんですね 同じ名前を使っちゃいけないことも知りませんでした(;'∀') 自分が無知すぎたorz ちなみに、リソースの回収、threading.join()とは何ですか? よくわからないので教えていただけると幸いです。
magichan

2018/09/19 23:47

thread.join() は要はthreadの停止(threadに割り振った関数が終了したこと)を待つ関数です。 上記のコードではstopフラグをセットにすることによりループを抜けさせて停止処理を行っているのですが、もっと複雑なコードになってくるとループ内で待ち状態になっていたり、重い処理を行っている等で直ぐにループを抜けるとは限らない訳す。(私のサンプルでもループ内sleep()がありますので、ループを抜けるまで1秒以上かかる可能性があるわけです) にも関わらず再度"start"が呼ばれて新しいthreadを生成してstopフラグをクリアする処理をしてしまうと、前のthreadも裏で同時に動き続けるという事態になってしまい、不具合の原因となりかねない上に、CPU資源やメモリー資源を無駄に消費してしまいます。 そのため、threadの停止処理を行う場合は thread.join() を呼びthreadが確実に終了(つまりCPU資源やメモリー資源の解放)してから次の処理を行うという実装にするほうが良いかと思います。
guest

0

主な問題点は

  • 関数stopとフラグ変数stopの名前が同じ

言語によっては関数的なものと変数的なものを同じ名前で定義できます。例えばJavaは同じ名前のフィールド(変数的なもの)とメソッド(関数的なもの)を同一クラスに定義できます。しかしPythonでは同じスコープ(本質問ではグローバル空間)に同じ名前の関数と変数を同時には定義できません。def stop(): ...で関数を定義しても、そのあとでstop = Trueなどとしてしまうと「stopという名前に格納されていた関数値がFalseで上書きされる」ため、stopはもはや関数ではなくなるのです。またこのような病的なコードを実行してもpythonインタープリタはエラーを報告しません。「他の値で上書きすることを前提として関数定義をする」という手法はまず使わないでしょうから本来は「defにより関数定義したら同じスコープでその関数名を他の目的(変数)では使わない」とすべきでしょう。
スレッドを使うコードに修正した際にEndボタンを押してもwhileループが完了しない原因はthreading.Thread(target=stop)を呼び出したときのstopの値が「関数ではなくTrueという値になってしまっている」からです。

  • Buttonのcommand引数

この引数に渡すべきは「ボタンが押された時点で実行すべき関数」です。質問者さんのコードでは
Button001=tk.Button(root,text="Start",command=thread_1.start())
となってますが、これではstartが起動されるのはボタンが押されたときではなく、ボタンを生成したときになってしまいます。
ボタンが押されたときにstartしたい場合は
Button001 = tk.Button(root, text="Start", command=thread_1.start)
などと書くべきです。スレッドを用いるようにしたコードではStartボタンを押さなくてもwhileループが始まっていますよね?

  • Endボタンの処理は別スレッドで行う必要なし

別スレッドで行うと動かなくなるわけではありませんが、そうすることに意味はありません。
Buttonのコンストラクターのcommand引数に指定するのはあくまで関数であり、それはボタンが押されたときに呼び出されます。またその関数が終了するまではtkinterのイベント処理は動かなくなります。それゆえEndStartボタンのcommandで直接whileループするような関数を指定するとご質問にあるような問題が起きます。これをスレッドを用いて回避すると
(A) ボタンを押したら別のスレッドを開始し即座に呼び出し元へ戻るようにする
(B) whileループは別のスレッドで実行する。
となるわけで「tkinterのイベント処理はメインスレッドで継続しつつ、whileループは別のスレッドで並行して動かす=>tkinterのイベント処理がwhileループにより阻害されないようにする」という考え方になります。一方Endボタンの処理は「単にフラグを変更する」だけなので、それを行うのにわざわざスレッドを動かす必要はありません。

  • startボタンは一度しか動作しません

同一のスレッドインスタンス(thread_1に代入されている値)を開始できるのは一度きりです。よってStart/Endボタンを複数回動かすならStartボタンを押すたびにスレッドインスタンスを新たに生成しなければならない点に注意してください。(一度だけでよいなら元のままでも構いません)

以上の点に着目し次のように修正してみました。

Python

1import tkinter as tk 2import threading 3 4 5def run(): # startという名前のままでもよいがわかりやすさのためrunにした 6 global frame, stop 7 8 stop = False # フラグの名前がstopなのですから停止させるまではFalseにするのが自然です 9 # 動いている間Trueであるようなフラグにするならrunningなどとすべきでしょう 10 # こういう「名前のおかしさ」は第三者のコードの理解を阻害します 11 while not stop: # 論理が冗長なので若干修正 12 print(frame) 13 frame += 1 14 15def on_start(): 16 threading.Thread(target=run).start() 17 18def on_stop(): 19 global stop 20 stop = True 21 22# ここからは関数外(on_stopなどはグローバルな関数であって普通メソッドとは呼びません) 23 24frame = 1 25root = tk.Tk() 26Button001 = tk.Button(root, text="Start", command=on_start) 27Button001.pack() 28Button002 = tk.Button(root, text="End", command=on_stop) 29Button002.pack() 30root.mainloop()

投稿2018/09/19 01:39

編集2018/09/19 03:44
KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KSwordOfHaste

2018/09/19 01:44

のんびりコメントしてたらmagichanさんとかぶった回答をしてしまいました。また自分の回答ではmagichanさん指摘のスレッドの後始末に言及できてませんでした~
tomo1998

2018/09/19 14:22

なんと! Python3では同じ名前を使うと上書きされてしまうんですね・・・知りませんでした(;'∀') 次回からは分かりやすいように名前を変えてみます~ Button Widgetのcommand引数はうっかりミスでした(-_-;) つい関数宣言と同じ感じで記入してしまい・・w 申し訳ない! 確かにEndボタンは別スレッドを使う必要なさそう 試してみたところスレッドインスタンスは確かに一度しか作動できないみたいですね・・・2度実行させようとしたらエラーが発生していました おかげさまでとてもよく理解&解決できました! ご丁寧な回答ありがとうございました!
guest

0

参考URL
簡易ストップウォッチを作ってみた @Python3

tkinter使用するのは初めてなので質問のコードをコピペして少しづつ削りながら動作確認してると
ほとんどが無くなりました。
最低限Endボタンが機能するようになったのでご参考までに。

動くか動かないかの違いはここだけの気がします。 if __name__ == '__main__':

Python3

1# coding=UTF-8 2import tkinter as tk 3import threading 4 5def start(): 6 print('start!') 7def stop(): 8 print('end!') 9 10# main 11if __name__ == '__main__': 12 root=tk.Tk() 13 Button001=tk.Button(root,text="Start",command=start) 14 Button001.pack() 15 Button002=tk.Button(root,text="End",command=stop) 16 Button002.pack() 17 root.mainloop()

投稿2018/09/18 23:32

opyon

総合スコア1009

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tomo1998

2018/09/19 14:28

ご回答ありがとうございます・・・っ! 確かにボタンは作動するのですが、ちょっと希望していたのと違う感じに~(;'∀') 説明が下手で申し訳ないです・・・ 「if __name__ == '__main__':」は、その実行されたプログラムがモジュールとして作動していなければ実行するコードなので、ちょっと関連性がない気がします・・・すいません><
opyon

2018/09/19 14:31

この回答した後自分でも作ってみてハマったので似たような質問してます。 よかったら参考までに御覧ください。
tomo1998

2018/09/19 14:36

なんと!早速見てみます!w
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問