なろう分析記録

『小説家になろう』をふくめ『ネット小説投稿サイト』を分析する。コード置き場,主にPython,javascript,たまに創作。

【Python GUI】「tkinter」で関数実行中にウィンドウがフリーズしてしまう問題を「threading」を使って回避する方法

tkinterGUIを作ると関数実行中に固まってしまう……

PythonGUI実装ライブラリはいくつか存在しますが、その中でも「tkinter」はPythonに最初から入っている最もシンプルなライブラリです。

今回は「tkinterで実装したGUIのボタンクリックで動作する処理がフリーズしてしまう問題」を「threading」を使うことで回避するサンプルコードをご紹介したいと思います。

f:id:karupoimou:20200427170210p:plain
フリーズ……

想定するアプリの動作

今回は「処理開始」ボタンをクリックすると、「待機中」と表示されているラベルの表示が「1..2..」という風に更新され、10秒後に最初の状態に戻る動作を想定してみます。

f:id:karupoimou:20200427165901p:plain:w400
最初
f:id:karupoimou:20200427165924p:plain:w400
途中
f:id:karupoimou:20200427165901p:plain:w400
最後

問題が起きるコード

import tkinter
import time

####

# ボタンクリック後の動作
def button_clicked():
    
    # ボタン非表示化
    button.pack_forget()
    
    # カウント処理
    for i in range(10):
        time.sleep(1)    
        var.set(i+1)
    
    var.set("待機中")
    
    # 最後にボタンを復元
    button.pack()  

####

# Windowの設定
root = tkinter.Tk()
root.title("tkinterサンプル")
root.geometry("300x150")

# ラベル表示
var = tkinter.StringVar()
var.set("待機中")
label = tkinter.Label(root, textvariable=var)
label.pack()

# ボタン表示
button = tkinter.Button(root, text='処理開始',command=button_clicked)
button.pack()

root.mainloop()

f:id:karupoimou:20200427164820p:plain:w400
最初
f:id:karupoimou:20200427170210p:plain:w400
途中
f:id:karupoimou:20200427164820p:plain:w400
最後

上記のコードは一見動きそうですが「処理開始」ボタンを押すとウィンドウがフリーズし、処理が全て終わるまで固まったままラベルの更新も行われません。

そこでこの問題を「threading」を用いることで回避します。

threadingでプロセスを分ける

ここではthreadingを用いることで「表示を更新する処理」と「数え上げる処理」を別々のプロセスに分けます。

なお、threadingは最初からPythonに入っているのでそのままimportできます。

import threading

動作するサンプルコード

import tkinter
import time
import threading

####

# ボタンクリック後の動作
def button_clicked():
    
    # ボタン非表示化
    button.pack_forget()
    
    # スレッディングでフリーズしないカウントダウン処理
    thread1 = threading.Thread(target=work1)
    thread1.start()    
    
# スレッドでラベル表示を更新
def work1():
    global var
    
    # カウント
    for i in range(10):
        time.sleep(1)    
        var.set(str(i+1))
    
    var.set("待機中")
    
    # 最後にボタンを復元
    button.pack()

####

# Windowの設定
root = tkinter.Tk()
root.title("tkinterサンプル")
root.geometry("300x150")

# ラベル表示
var = tkinter.StringVar()
var.set("待機中")
label = tkinter.Label(root, textvariable=var)
label.pack()

# ボタン表示
button = tkinter.Button(root, text='処理開始',command=button_clicked)
button.pack()

root.mainloop()
コードの解説
button = tkinter.Button(root, text='処理開始',command=button_clicked)

ここで「button」をクリックすることで、button_clicked()が実行される様にしています。

# ボタンクリック後の動作
def button_clicked():
    
    # ボタン非表示化
    button.pack_forget()
    
    # スレッディングでフリーズしないカウントダウン処理
    thread1 = threading.Thread(target=work1)
    thread1.start()    

ここではボタンを一旦非表示にしたあと、threadingによって「def work1()」を別プロセスとして開始しています。

# スレッドでラベル表示を更新
def work1():
    global var
    
    # カウント
    for i in range(10):
        time.sleep(1)    
        var.set(str(i+1))
    
    var.set("待機中")
    
    # 最後にボタンを復元
    button.pack()

ここでは「var」をグローバル変数化し、「var.set("")」で順次その中身を入れ替えることでラベルの表示を更新するように指定しています。