· 

Python+tkinterでマルチタスク風

Pythonでマルチタスクを実現するにはThreadを使うのが一般的ですが、tkinterを使ったGUIを含むプロジェクトなら、afterってやつでマルチタスク風にできます。

特に、今日は条件を変えながら回数指定で一定間隔(または周期)で繰り返し何かをやるってことに特化して取り組みます。特定の職種では非常によくあるシチュエーションです。time.sleepだと、GUIが固まったようになっちゃうし、何しろ繰り返しを始めたら止める手段はスクリプトの強制終了(Ctrl+c)しかない。

 

で、afterって何かっていうと、、、指定した時間が経過したら何かする予約をする(あくまで予約するだけ)。で、一定時間経過したときに「何か」をするわけだ。でその「何か」の中で「「何か」をする予約」をすれば一定時間でず~っと繰り返してやってくれる。、、、まぢややこしや。

 

はまるのが、繰り返しなんだからforでafterをやってしまう間違い。forで「「何か」の予約」を繰り返しやってしまうだけ。

 

さて、回数限定となると、「何か」の中で「「何か」をする予約」をするときに決められた回数をこなしたかどうかを知っている必要がある。こなしきっていれば次の「何か」を予約しない。予約をしなければ止まる。が、afterでは「何か」の関数に引数を1つしか渡せない。よってここでは条件とか回数とかを辞書にまとめて、辞書を渡すことにする。辞書には最低、今何回目なのかと何回するのかの情報が必要。で「何か」の中で、今何回目なのかをインクリメントする。、、、まぢややこしや。自分でも何かいているのかわからなくなってきた。

 

で、めんどくさいので能書きはこれくらいにして、やってみた。

今回の例は、2重ループで外側のループでは条件変更がある。内側のループは繰り返し実行するだけ。、、、ほんとマジでよくあるシチュエーションです。途中で止めるafter_cancelも使っています。こんかいのコードでは繰り返し実行の"間隔"をintervalにしていることになります。「何か」の中の最初のほう、遅くともexecute_somethingの前で次の「「何か」をする予約」をすれば、実行の"周期"をintervalにすることになります。(たぶん)

  1. import tkinter as tk
  2. class Application(tk.Frame):
  3.     def __init__(self,master=None):
  4.         super().__init__(master)
  5.         self.pack()
  6.         master.geometry('180x80')
  7.         master.title('Test')
  8.         button_start=tk.Button(master,text="Start",command=self.button_start_pressed)
  9.         button_stop=tk.Button(master,text="Stop",command=self.button_stop_pressed)
  10.         button_start.pack(side='left')
  11.         button_stop.pack(side='left')
  12.     
  13.     def button_start_pressed(self):
  14.         interval=500
  15.         ntry1=5
  16.         itry1=0
  17.         ntry2=3
  18.         itry2=0
  19.         settings1=[10,-8.8,30,40,100]
  20.         args={
  21.             "interval":interval,
  22.             "ntry1":ntry1,
  23.             "itry1":itry1,
  24.             "settings1":settings1,
  25.             "ntry2":ntry2,
  26.             "itry2":itry2
  27.         }
  28.         print('button_start_pressed')
  29.         self.test_function_repeat_w_after(args)
  30.     
  31.     def button_stop_pressed(self):
  32.         print('button_stop_pressed')
  33.         self.master.after_cancel(self.after_id)
  34.     def test_function_repeat_w_after(self,args):
  35.         interval=args['interval']
  36.         ntry1=args['ntry1']
  37.         itry1=args['itry1']
  38.         settings1=args['settings1']
  39.         ntry2=args['ntry2']
  40.         itry2=args['itry2']
  41.         print(itry1+1,end='/')
  42.         print(ntry1,end='\t')
  43.         print(itry2+1,end='/')
  44.         print(ntry2,end='\t')
  45.         execute_something(settings1[itry1])
  46.         itry2=itry2+1
  47.         if itry2==ntry2:
  48.             itry2=0
  49.             itry1=itry1+1
  50.         args['itry1']=itry1
  51.         args['itry2']=itry2
  52.         if itry1<ntry1:
  53.             self.after_id=self.master.after(interval,self.test_function_repeat_w_after,args)
  54. def execute_something(i):
  55.     print('execute something... with a setting',end=':')
  56.     print(i)
  57. def main():
  58.     win=tk.Tk()
  59.     app=Application(master=win)
  60.     app.mainloop()
  61. if __name__=='__main__':
  62.     main()

ちょっと見にくいけど実行した結果です。500ms感覚で繰り返し実行している、、、はず。

Stopボタンもちゃんと機能している。

今回はきっと誰かの役に立つ内容だったに違いない。

tkinterを使うときにclassにするか、平たく書くか悩みましたが、今回はclassにしています。正直どっちがいいのかさっぱりわからん。classの特徴って、継承とか複数インスタンスとかアクセスコントロールとかだと思うけど、tkinterのGUIでそんなこと考えないよなー、、、ってことは、コードの中の字数を増やしているだけのような、、、

 

1回目は翌日の夕方から頭痛と発熱があったので、今回(2回目)もそろそろ来そうな予感がする。エタノールを飲んで寝るしかない。