Home > database >  Why is it doesn't the function run?
Why is it doesn't the function run?

Time:03-19

Ok so I have multiple problems with the code under:

  1. when the key chosen in the Combo Box is held down, it keeps printing "You Pressed It", is there a way to avoid this?
  2. When I press the set hotkey, the label changes but the while loop in the autofisher() doesnt, its suppose to do a process of tasks but I simplified it to print for this question.
run = False

def press():
    global run
    while True:
        if keyboard.read_key(hotCombo.get()):
            print("You Pressed It")
            run = not run
            keyboard.wait(hotCombo.get())
            if run == True:
                status["text"]="Working"
            else:
                status["text"]="Not Working"
            
def autofisher():
    while run == True:
        print("runnning")

Been tinkering with it and found more problems

I ended up with this but while its printing run I cant seem to stop it

def autofisher():
    global run
    while True:
        if keyboard.read_key(hotCombo.get()):
            print("kijanbdsjokn")
            run = not run
            keyboard.wait(hotCombo.get())
            if run == True:
                status["text"]="Working"
            else:
                status["text"]="Not Working"
            while run == True:
                print("run")
                time.sleep(1)
            

CodePudding user response:

Can I ask why I cant just integrate tkinter into a working python script using threading?

A Python script is generally linear. You do things in sequence and then you exit.

In a tkinter program, your code consists of three things.

  • Code to set up the window and widgets.
  • Initialization of global variables (doesn't really matter if you hide them in a class instance; they're still globals).
  • Most of it will be functions/methods that are called as callbacks from tkinter when it is in the mainloop.

So in atkinter program most of your code is a guest in the mainloop. Where it is executed in small pieces in reaction to events. This is a completely different kind of program. It was called event-driven or message based programming long before that became cool in web servers and frameworks.

So, can you integrate a script in a tkinter program? Yes, it is possible. There are basically three ways you can do it;

  • Split the code up into small pieces that can be called via after timeouts. This involves the most reorganization of your code. To keep the GUI responsive, event handlers (like timeouts) should not take too long; 50 ms seems to be a reasonable upper limit.
  • Run it in a different thread. We will cover that in more detail below.
  • Run it in a different process. Broadly similar to running in a thread (the API's of threading.Thread and multiprocessing.Process are almost the same by design). The largest difference is that communication between processes has to be done explicitly via e.g. Queue or Pipe.

There are some things that you have to take into account when using extra threads, especially in a tkinter program.

1) Python version

You need to use Python 3. This will not work well in Python 2 for reasons that are beyond the scope of this answer. Better preemption of threads in Python 3 is a big part of it.

2) Multithreaded tkinter build

The tkinter (or rather the underlying tcl interpreter) needs to be built with threading enabled. I gather that the official python.org builds for ms-windows are, but apart from that YMMV. On some UNIX-like systems such as Linux or *BSD the packages/ports systems gives you a choice in this.

3) Make your code into a function

You need to wrap up the core of your original script in a function so you can start it in a thread.

4) Make the function thread-friendly

You probably want to be able to interrupt that thread if it takes too long. So you have to adapt it to check regularly if it should continue. Checking if a global named run is True is one method. Note that the threading API does not allow you to just terminate a thread.

5 The normal perils of multithreading

You have to be careful with modifying widgets or globals from both threads at the same time.

At the time of writing, the Python GIL helps you here. Since it assures that only one thread at a time is executing Python bytecode, any change that can be done in a single bytecode is multithreading safe as a side effect.

For example, look at the modification of a global in the modify function:

In [1]: import dis

In [2]: data = []
Out[2]: []

In [3]: def modify():
   ...:     global data
   ...:     newdata = [1,2,3]
   ...:     data = newdata
   ...:     

In [4]: dis.dis(modify)
  3           0 BUILD_LIST               0
              2 LOAD_CONST               1 ((1, 2, 3))
              4 LIST_EXTEND              1
              6 STORE_FAST               0 (newdata)

  4           8 LOAD_FAST                0 (newdata)
             10 STORE_GLOBAL             0 (data)
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE

See how the new list is built separately, and only when it is comple is it assigned to the global. (This was not by accident.)

It takes only a single bytecode instruction (STORE_GLOBAL) to set a global variable to a newly created list. So at no point can the value of data be ambiguous.

But a lot of things take more then one bytecode. So there is a chance that one thread is preempted in favor of the other while it was modifying a variable or widget. How big of a chance that is depends on how often these situations happen and how long they take.

IIRC, currently a thread is preempted every 15 ms. So an change that takes longer than that is guaranteed to be preempted. As is any task that drops the GIL for I/O.

So if you see weird things happening, make sure to use Locks to regulate access to shared resources.

It helps if e.g. a widget or variable is only modified from one thread, and only read from all the other threads.

CodePudding user response:

One way to handle your key is to turn it into a two-phase loop:

def press():
    global run
    while True:
        while not keyboard.read_key(hotCombo.get()):
            time.sleep(0.2)
        run = True
        status["text"]="Working"
        while keyboard.read_key(hotCombo.get()):
            print("running")
            time.sleep(0.2)
        run == False
        status["text"]="Not Working"
  • Related