Ok so I have multiple problems with the code under:
- when the key chosen in the Combo Box is held down, it keeps printing "You Pressed It", is there a way to avoid this?
- 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 themainloop
.
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
andmultiprocessing.Process
are almost the same by design). The largest difference is that communication between processes has to be done explicitly via e.g.Queue
orPipe
.
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 Lock
s 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"