0
from pythoncom import PumpWaitingMessages 
import pyHook, threading 
import tkinter as tk

threadsRun = 1 
token = 0

def pas():
  while threadsRun:
    pass

def listen(startButton): 
    """Listens for keystrokes"""

    def OnKeyboardEvent(event):
        """A key was pressed"""
        global threadsRun
        if event.Key == "R":
            startButton.config(relief=tk.RAISED, state=tk.NORMAL, text="Start")
            threadsRun = 0
        return True

    hm = pyHook.HookManager()
    hm.KeyDown = OnKeyboardEvent
    hm.HookKeyboard()
    while threadsRun:
        PumpWaitingMessages()
    else:
        hm.UnhookKeyboard()

def simplegui():

    def threadmaker():
        """Starts threads running listen() and pas()"""
        startButton.config(relief=tk.SUNKEN, state=tk.DISABLED, text="r=stop listening")
        global token, threadsRun
        threadsRun = 1
        token += 1
        t1 = threading.Thread(target=pas, name='pas{}'.format(token))
        t2 = threading.Thread(target=listen, args=(startButton,), name='listen{}'.format(token))
        t1.start()
        t2.start()

    def destroy():
        """exit program"""
        global threadsRun
        threadsRun = 0
        root.destroy()

    startButton = tk.Button(root, text="Start", command=threadmaker, height=10, width=20)
    startButton.grid(row=1, column=0)

    quitButton = tk.Button(root, text="Quit", command=destroy, height=10, width=20)
    quitButton.grid(row=1, column=1)

root = tk.Tk() 
simplegui() 
root.mainloop()

Code description:
simplegui() creates two threads to run
pas() and
listen()
simultaneously.

listen() waits for keyboard presses(only r does anything: exits both threads/functions).
pas() does nothing but is needed to reproduce bug.

Problem description:
After clicking start, pressing any button on the keyboard can cause tkinter to stop responding.
~2/3rd of the time r will behave as intended.

I'm using Spyder IDE (python 3.5).

Some observations:
Using print statements, the program will go into while threadsRun loop, in listen(), before the crash, but didn't reach OnKeyboardEvent() print statement.
Can wait a long time before pressing a key and it may freeze.
Can press a key instantly after pressing start and it may function as intended.
Removing t1 = ... and t1.start() lines allows program to run bug free.
Alternatively, removing all tkinter code allows program to run bug free.
Mashing a bunch of keys all at once freezes it.
If I place a print statement inside the while threadsRun loop, r will rarely work.
I've read in other posts, tkinter is not thread safe, and to use a queue. But I don't understand how. I also think maybe something else is wrong because it works sometimes. https://www.reddit.com/r/learnpython/comments/1v5v3r/tkinter_uis_toplevel_freezes_on_windows_machine/

Thanks very much for reading.

R4PH43L
  • 2,122
  • 3
  • 18
  • 30
ant9985
  • 9
  • 2
  • Thanks for edit and answer R4PH43L. I wasn't able to implement class and queue but it did motivate me to learn classes which is good :). I found this thread: https://stackoverflow.com/questions/42101906/combining-tkinter-mainloop-with-another-event-listener?rq=1 I was able to reconstruct the code without `pumpwaitingmessages()`, So it may have been having two messagepumps(I'm assuming there is one in `mainloop()`), that caused the lockup. My new code works as intended now. – ant9985 Feb 09 '18 at 12:37

1 Answers1

0

One attempt I managed to use for threads and queues is the following (replaced used code with multiple pseudo-code entries)

The Class works as a session watchdog and uses sql commands gather logged in users, then uses threads for their location detection (geoip)

class SessionWatchdog

import Tkinter as tk
import ttk
import Queue

import Locator

class SessionWatchdog(ttk.Frame):
    """
    Class to monitor active Sessions including Location in a threaded environment
    """
    __queue = None 
    __sql = None

    def __init__(self, *args, **kwargs):
        #...
        # Create the Queue
        self.__queue = Queue.Queue()

    def inqueue(self):
        """ Handle Input from watchdog worker Thread """

        if self.__queue.empty():
            return

        while self.__queue.qsize():
            """
                Use 
                try:
                    self.__queue.get()
                finally:
                    self.__queue.task_done()

                to retrieve data from the queue
            """
            pass

    def gather_data(self, queue):
        """
        Retrieve online users and locate them
        """
        if self.__sql:
            threads = []

            # gather data via sql
            # ....
            # ....
            for data in sql_result:
                thread = Locator(queue, data)
                threads.append(thread)
                thread.start()

            for thread in threads:
                thread.join()

Locator to fill the queue:

class Locator


import threading
import Queue

class Locator(threading.Thread):
    """
    docstring
    """

    __base_url = "http://freegeoip.net/json/{}"

    def __init__(self, queue, user_information):
        threading.Thread.__init__(self)

        self.queue = queue
        self.user_information = user_information
    def run(self):
        """ add location information to data (self.user_information)
            to improve performance, we put the localization in single threads.
        """
        located_user = []
        # locate the user in a function, NOT method!
        self.queue.put(located_user, False)
R4PH43L
  • 2,122
  • 3
  • 18
  • 30