at backyard

Color my life with the chaos of trouble.

tkinterとasyncioとthreadingを用いた投げっぱなしGUIプログラミング

asyncioとthreadingを用いた投げっぱなし処理について

asyncioとthreadingを用いた投げっぱなし処理について下記で書いた。

shinshin86.hateblo.jp

今回こちらで書いた処理をtkinterで作成したGUIアプリ上で試してみたコードを下記に貼る。
やはり実際にデスクトップアプリを書いていると投げっぱなし(fire and forget)の処理というのは結構必要になる場面が多い。

tkinterでのasyncioとthreadingを用いたサンプル

import asyncio
import threading
import tkinter as tk
import tkinter.messagebox as tkm
from time import sleep


class App:

    def __init__(self, root):
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        self.root = root
        root.title("Countdown sample")
        root.geometry("300x100")
        self.label = tk.Label(root, text="Please press one of the buttons")
        self.label.pack()
        tk.Button(root, text="Countdown start: 3", command=lambda:self.thread_countdown(3, "Call thread_countdown")).pack()
        tk.Button(root, text="Countdown start: 5", command=lambda:loop.run_in_executor(None, self.countdown, 5, "Call countdown")).pack()


    def countdown(self, count, msg):
        countdown_text = "Countdown: "
        for i in range(count, 0, -1):
            self.label["text"] = countdown_text + str(i)
            sleep(1)
        self.label["text"] = countdown_text + "0"
        sleep(1)
        tkm.showinfo("FINISH", msg)


    def thread_countdown(self, count, msg):
        thread = threading.Thread(target=self.countdown, args=(count, msg))
        thread.start()


def main():
    root = tk.Tk()
    app = App(root)
    app.root.mainloop()


if __name__ == "__main__":
    main()

こちらのコードを実行すると、下記のような画面が表示される。
それぞれのボタンを押すことでasyncioとthreadingそれぞれの処理を動かすことができる。

f:id:shinshin86:20220210233640p:plain
それぞれのボタンを押すとカウントダウンを開始する

上に貼ったサンプルコードをもとに、それぞれの処理に関する詳細を書いておく。

asyncioを用いた投げっぱなし処理

    def __init__(self, root):
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        ・
        ・
        ・
        tk.Button(root, text="Countdown start: 5", command=lambda:loop.run_in_executor(None, self.countdown, 5, "Call countdown")).pack()

asyncioに関する処理は一番上に貼ったポストで書いたとおりで、ここでは __init__ 内でイベントループを作成している。
tkinterでボタン押下時のイベントハンドラの設定はcommandで行うので、 command=lambda:loop.run_in_executor(None, self.countdown, 5, "Call countdown") という箇所で投げっぱなし処理を実現している。

threadingを用いた投げっぱなし処理

変わってthreadingの方はイベントハンドラへの設定は普段の設定と変わらずに下記のように行っている。

        tk.Button(root, text="Countdown start: 3", command=lambda:self.thread_countdown(3, "Call thread_countdown")).pack()

が、代わりに関数の呼び出し側でスレッドを別に作成し、そちらでスレッドをスタートさせて処理を投げっぱなしにさせている。

    def countdown(self, count, msg):
        # カウントダウン処理


    def thread_countdown(self, count, msg):
        thread = threading.Thread(target=self.countdown, args=(count, msg))
        thread.start()

ちなみにthreading.Threadで関数を実行する際に引数を渡す必要がある場合、別途 argsから関数を渡す必要がある。
最初普通に thread = threading.Thread(target=self.countdown(count, msg)) としてしまっていて、想定通りに動かずに10分ほどハマった。
(ちゃんとドキュメントを読めという話だった...)

なお、該当するドキュメントは下記となる。

threading --- スレッドベースの並列処理 — Python 3.10.0b2 ドキュメント

以上、tkinterとasyncioとthreadingに関する備忘録でした。

なお、コードはGitHubにも上げています。

github.com