Yu_Poke
@Yu_Poke

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

Linuxにてpynputとthreadingを組み合わせるとフリーズします

解決したいこと

Pythonでpynputのキー入力をON/OFFする機構を
検討しているのですが、Linux上でthreadingを使用すると
処理がフリーズします。

解決方法をご教授頂けますでしょうか?

プログラムの説明と発生している問題

tkinterのウィンドウにて
ボタンでpynputの入力受付のON/OFFを行い、
ラベルにて正常にプログラムが動くことの確認として
1秒毎にカウントアップするプログラムです。

コード1の場合はthreadingにてカウントアップを行い、
コード2の場合はtkinterのafter()メソッドにてカウントアップを行っています。

Windows上ではコード1、コード2ともに
正常にpynputの入力受付のON/OFFが行えるのですが、
Linux上ではコード1だとkey_disable()内の
self.key_listener.stop()のタイミングで
プログラムがフリーズします。

コード2はLinux上でも正常に動作するのですが、最終的にはthreadingを用いて描画とは別で動かしたい処理があるため、解決策を模索しております。

コード1

import tkinter as tk
from pynput.keyboard import Key, Listener
import time
from threading import Thread

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("ボタンの作成")     # ウィンドウタイトル
        self.master.geometry("200x100")       # ウィンドウサイズ(幅x高さ)
        self.flag = False
        self.button_text = tk.StringVar()
        self.label_text = tk.StringVar()
        self.button_text.set("OFF")
        self.counter = 0
        self.label_text.set(str(self.counter))
        self.key_listener = None
        self.thread = Thread(target=self.counter_thread, args=())
        self.thread_flag = True
        self.thread.start()
        self.master.protocol("WM_DELETE_WINDOW", self.exit)

        #--------------------------------------------------------
        # ボタンの作成
        self.button = tk.Button(self.master,
                                textvariable=self.button_text,
                                command = self.button_click
                                )

        self.button.grid(column='0', row='0', padx='5', pady='5', sticky='ew')
        self.label = tk.Label(self.master, textvariable=self.label_text)
        self.label.grid(column='1', row='0', padx='5', pady='5', sticky='ew')

        #--------------------------------------------------------

    def button_click(self):
        if self.flag == True:
            print("Key入力OFF")
            self.key_disable()
        else:
            print("Key入力ON")
            self.key_enable()

    def counter_inc(self):
        self.counter += 1
        self.label_text.set(str(self.counter))

    def counter_thread(self):
        while self.thread_flag:
            self.counter_inc()
            time.sleep(1)

    def key_enable(self):
        self.flag = True
        if self.key_listener == None:
            self.button_text.set("ON")
            self.key_listener = Listener(on_press=self.on_press,
                                         on_release=self.on_release)
            self.key_listener.start()

    def key_disable(self):
        self.flag = False
        if self.key_listener != None:
            self.button_text.set("OFF")
            self.key_listener.stop()
            self.key_listener = None

    def on_press(self, key):
        print('{0} pressed'.format(key))

    def on_release(self, key):
        print('{0} released'.format(key))

    def exit(self):
        self.thread_flag = False
        self.thread.join()
        self.key_disable()
        self.master.destroy()

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.key_enable()
    app.mainloop()

コード2

import tkinter as tk
from pynput.keyboard import Key, Listener
import time
from threading import Thread

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)

        self.master.title("ボタンの作成")     # ウィンドウタイトル
        self.master.geometry("200x100")       # ウィンドウサイズ(幅x高さ)
        self.flag = False
        self.button_text = tk.StringVar()
        self.label_text = tk.StringVar()
        self.button_text.set("OFF")
        self.counter = 0
        self.label_text.set(str(self.counter))
        self.key_listener = None
        self.master.protocol("WM_DELETE_WINDOW", self.exit)

        #--------------------------------------------------------
        # ボタンの作成
        self.button = tk.Button(self.master,
                                textvariable=self.button_text,
                                command = self.button_click
                                )

        self.button.grid(column='0', row='0', padx='5', pady='5', sticky='ew')
        self.label = tk.Label(self.master, textvariable=self.label_text)
        self.label.grid(column='1', row='0', padx='5', pady='5', sticky='ew')

        self.master.after(1000, self.counter_after)
        #--------------------------------------------------------

    def button_click(self):
        if self.flag == True:
            print("Key入力OFF")
            self.key_disable()
        else:
            print("Key入力ON")
            self.key_enable()

    def counter_inc(self):
        self.counter += 1
        self.label_text.set(str(self.counter))

    def counter_thread(self):
        while self.thread_flag:
            self.counter_inc()
            time.sleep(1)

    def counter_after(self):
        self.counter_inc()
        self.master.after(1000, self.counter_after)

    def key_enable(self):
        self.flag = True
        if self.key_listener == None:
            self.button_text.set("ON")
            self.key_listener = Listener(on_press=self.on_press,
                                         on_release=self.on_release)
            self.key_listener.start()

    def key_disable(self):
        self.flag = False
        if self.key_listener != None:
            self.button_text.set("OFF")
            self.key_listener.stop()
            self.key_listener = None

    def on_press(self, key):
        print('{0} pressed'.format(key))

    def on_release(self, key):
        print('{0} released'.format(key))

    def exit(self):
        #self.thread_flag = False
        #self.thread.join()
        self.key_disable()
        self.master.destroy()

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.key_enable()
    app.mainloop()
0

1Answer

私はLinuxユーザーではないし、実行環境がわからないのでこの回答が有効かわからないのですが……。

Pythonインタプリタでは、プログラムが動かせるスレッドの最大値が決まっています。このために、少なくとも私の環境ではthreadingは全く動きません。(設定を調整したり、インタプリタではなく実行ファイル化したりすれば確か動く)

その代わりに、マルチプロセスはインタプリタでも動くはずです。
マルチプロセスに変えてみるか実行環境や設定を見直してみると、もしかしたら解決するかもしれません。

0Like

Your answer might help someone💌