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

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

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

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

Q&A

解決済

3回答

3343閲覧

threadingで呼び出した以前のループメソッドを停止したい

tomo1998

総合スコア34

Python 3.x

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

1グッド

0クリップ

投稿2019/04/30 16:42

前提・実現したいこと

ボタンを押すとループで1から3までカウントし始めて、ボタンを押しなおすとカウントを中止し、また最初から1から3までカウントする といったプログラムを作っています

ボタンを押すとthreadingでループするメソッドを呼び出し、ボタンをもう一度押すとループを停止するフラグを成立させてまたループするメソッドを呼び出そうとしていますがうまく行きません

該当のソースコード

python3

1import threading 2import tkinter as tk 3import time 4 5LoopFlg=True 6 7def Count(): 8 global LoopFlg 9 LoopFlg=True 10 while LoopFlg==True: 11 if LoopFlg!=True: 12 break 13 time.sleep(1) 14 if LoopFlg!=True: 15 break 16 print(1) 17 18 time.sleep(1) 19 if LoopFlg!=True: 20 break 21 print(2) 22 23 if LoopFlg!=True: 24 break 25 time.sleep(1) 26 print(3) 27 28def Call_Count(): 29 global LoopFlg 30 LoopFlg=False 31 threading.Thread(target=Count).start() 32 33root=tk.Tk() 34button=tk.Button(root,text="Count",command=Call_Count) 35button.pack() 36root.mainloop() 37

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

ボタンを押しなおすと以前のループは停止せず、そのままカウントし始めるのでその結果複数のループが発生してしまいます

試したこと

https://qiita.com/xeno1991/items/b207d55a413664513e5f
のサイトをちょっと読んで真似てみたのですがうまく行きませんでした。
多分、time.sleep()によって処理が止まっている間にボタン押してもループはフラグ成立したことが分からないからなのかなぁ・・・?

import threading import tkinter as tk import time class App(): def __init__(self): self.stop_event = threading.Event() #停止させるかのフラグ root=tk.Tk() button=tk.Button(root,text="Count",command=self.call_count) button.pack() root.mainloop() def call_count(self): self.stop_event.set() self.stop_event = threading.Event() threading.Thread(target = self.target).start() def target(self): while not self.stop_event.is_set(): time.sleep(1) if self.stop_event.is_set(): break print(1) if self.stop_event.is_set(): break time.sleep(1) if self.stop_event.is_set(): break print(2) time.sleep(1) if self.stop_event.is_set(): break print(3) app=App()
kotai2001👍を押しています

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

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

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

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

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

guest

回答3

0

期待通りにならない原因はhayataka2049さんが回答されているので省略します。

このようなプログラムの設計の仕方は色々考えられます。
(A)「ボタンを押す度にスレッドを起こす」方法
(B)「単一のスレッドでカウントアップをし続ける」方法
(C)「afterメソッドを用いてスレッドを起こさない」方法
GUIアプリケーションの定期的処理は(C)が一番自然な気がしますが、ボタンを押してから1秒後に1を表示する前提で制御を考えると(A)も悪い方法ではないかも知れません。

対処:

とりあえず元のコードのとおり(A)を前提とするなら「一つのフラグをあちこちのスレッドからアクセスするのではなく、スレッドごとに専用のフラグを持つ」のがわかりやすい対処であるような気がします。同期待ちのようなことを一切せずに済みますので。

python

1import threading 2import tkinter as tk 3import time 4 5 6class App(tk.Frame): 7 def __init__(self, master): 8 super().__init__(master) 9 self.pack() 10 button = tk.Button(self, text="Count", command=self.on_count) 11 button.pack() 12 self.thread = None 13 14 def on_count(self): 15 if self.thread is not None: 16 self.thread.stop_requested = True 17 self.thread = Counter() 18 19 20class Counter(threading.Thread): 21 def __init__(self): 22 super().__init__(daemon=True) 23 self.stop_requested = False 24 self.start() 25 26 def run(self): 27 while True: 28 for i in range(3): 29 time.sleep(1) 30 if self.stop_requested: 31 break 32 print(i + 1) 33 34 35root = tk.Tk() 36app = App(root) 37root.mainloop()

最初can110さんのようにjoinにより既スレッドが止まるのを待ってから次のスレッドを起動・・・と考えたのですが、

・GUIスレッドでThread.joinやtime.sleepなどスレッドをブロックする処理は避ける
・ボタンを押す度に1秒経過してから最初の数値「1」を表示する
・極力単純に・・・

あたりを意識して上のような方法を提案してみました。

投稿2019/04/30 21:52

KSwordOfHaste

総合スコア18392

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

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

tomo1998

2019/05/01 06:58

とっても興味深い回答ありがとう! KSwordOfHasteさんの書いたself.threadのような使い方は想像できなかったのでビックリです かなりスマートな書き方だと思います! ありがと!
guest

0

ベストアンサー

python

1 LoopFlg=False 2 threading.Thread(target=Count).start()

が実行されたあと、Count関数の2行目でLoopFlg=Trueされてしまいます。この間に判定が走れば上手くいくかもしれませんが、極めて稀なケースです。

一番直感的な解決は、LoopFlg=Falseのあとに1秒以上待ってみることです。

python

1def Count(): 2 global LoopFlg 3 time.sleep(1.05) # こっちに書かないとメインスレッドを止めます 4 5 LoopFlg=True 6

これはほぼ期待通り動きます。とはいえ、余計に待たされてダサいですね。

追記:can110さんのjoinする方法もシンプルで良いと思います。以下の方法とは若干動作が異なります(1秒を待ってから次のスレッドが起動するか、待たないでそのまま切り替わるか)。

押した瞬間に切り替わるようにしたければ、イベントを使うと良いでしょう。ちなみにtime.sleepしている間は何もできませんが、Event.waitであればタイムアウト時間を設定して待たせることも可能です。

全体的に書き換えたコード。命名規則はPEP8に合わせています。

python

1import threading 2import tkinter as tk 3 4def count(): 5 event.clear() 6 x = 0 7 while True: 8 print(x % 3 + 1) 9 if event.wait(timeout=1.0): 10 break 11 x += 1 12 13def call_count(): 14 event.set() 15 threading.Thread(target=count, daemon=True).start() 16 17root=tk.Tk() 18button=tk.Button(root,text="Count",command=call_count) 19button.pack() 20 21event = threading.Event() 22root.mainloop() 23

単一のスレッドで回し続けると、カウンタ変数を書き換えるだけで簡単に実現できます。

python

1import time 2import threading 3import tkinter as tk 4 5def count(): 6 global i 7 i = 0 8 while True: 9 print(i % 3 + 1) 10 i += 1 11 time.sleep(1) 12 13def call_count(): 14 global i 15 i = 0 16 if not th.isAlive(): 17 th.start() 18 19root=tk.Tk() 20button=tk.Button(root,text="Count",command=call_count) 21button.pack() 22 23th = threading.Thread(target=count, daemon=True) 24root.mainloop()

投稿2019/04/30 17:24

編集2019/04/30 22:17
hayataka2049

総合スコア30933

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

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

tomo1998

2019/05/01 06:52

なるほど・・・色々代案ありがとうございます。 print(x % 3 + 1)と書くことで1から3までのカウントも出来るんですね、ビックリです eventの使い方についても良くわかりました ありがとうございましたm(_ _)m
guest

0

以下のようにスレッド動作していたら.joinで終了待ち後に再生成することで目的の動作ができると思います。

Python

1LoopFlg=True 2def Count(): 3 # 略 4 5def Call_Count(): 6 global LoopFlg,t 7 LoopFlg=False 8 if t.isAlive(): 9 t.join() 10 t = threading.Thread(target=Count) # スレッド生成 11 t.start() 12 13t = threading.Thread(target=Count) # スレッド生成 14root=tk.Tk() 15button=tk.Button(root,text="Count",command=Call_Count) 16button.pack() 17root.mainloop()

投稿2019/04/30 17:17

can110

総合スコア38234

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問