How to thread two functions where one output effect the others actions - python

Here's my code:
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
hshtag = int(0)
done = False
fire = False
def StopStart(fire):
while not done:
global fire
if keyboard.is_pressed('#'):
hshtag = hshtag + 1
if hshtag % 2 ==0:
fire = False
else:
fire = True
return fire
def NormalFire():
while not done:
global fire
if fire == True:
#do x
else:
pass
t1 = Thread(target = StopStart)
t2 = Thread(target = NormalFire(fire))
t1.start()
t2.start()
The problem is function StopStart (should) effect what function Normalfire does but as the function only accepts a value for fire when it starts running (so it doesn't work). What I want is change what function normalfire does with function stopstart. and if you're wondering why I am using threading it because '#do x' actually takes a while to work so as one continuous script if I clicked hash at the wrong time it wouldn't stop. Maybe I could do this with classes instead but im not good with classes so if someone could either help with that or fix the above code that would be great thanks.
New attempt at explaining what's wrong with the top code - Ok, so both functions should be running simultaneously (which they are) - so no problems there. but as the function StopStart changes the boolean fire to true/false I want that to cause my NormalFire function to change what it is doing - nothing when I haven't clicked hash yet and something if I've clicked hash once but if I then click hash while its running it will finish whats its running then do nothing waiting for hash to be clicked again.
Sorry, my question wasn't clear take this code as a simplification of my core question.
##imports
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
##variable assigning
hshtag = int(0)
done = False
fire = False
def x():
while not done:
fire = True
return fire
def y(fire):
while not done:
if fire:
print('ok')
else:
pass
t1 = Thread(target = x)
t2 = Thread(target = y(fire))
t1.start()
t2.start()
Currently, the above code outputs nothing even though I've set 'fire = true' in function x and returned it how would I edit this code so that when boolean fire changes to true the function y starts printing ok?
Editing like Nair suggested also returns nothing and after 15 second the program stops running edited code:
##imports
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
##variable assigning
hshtag = int(0)
done = False
fire = False
def StopStart():
while not done:
fire = True
return fire
def NormalFire():
while not done:
if fire:
print('ok')
else:
pass
t1 = Thread(target = StopStart)
t2 = Thread(target = NormalFire)
t1.start()
t2.start()

I'm unable to comment so I apologize in advanced. I'm having trouble understanding your question above, but I reworked your code - fix/add whatever you need and get back to me!
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
hshtag = int(0)
done = False
fire = False
def StopStart():
while not done:
# global fire - You're setting StopStart up for a param that needs passed, that also is named another variable
# So it will just over write it (Also, no arg is passed for StopStart(fire))
if keyboard.is_pressed('#'):
hshtag = hshtag + 1
if hshtag % 2 == 0 : fire = False
else : fire = true
return fire
def NormalFire():
while not done:
#global fire - Don't need to global it, you would've had to global done if that was the case
if fire: # don't need == true, just need if fire (if true)
print("x")
t1 = Thread(target=StopStart)
t2 = Thread(target=NormalFire)
t1.start()
t2.start()

Not sure that this is exactly what you're asking for. I would maybe listen for keyboard events outside of either thread. Instead, just bind keyboard events to a callback which set a threading.Event object. Sorry for the weird and slightly morbid example:
from pynput import keyboard
from threading import Thread, Event
plate_dropped = Event()
def on_press(key):
if key is keyboard.Key.enter:
plate_dropped.set()
listener = keyboard.Listener(on_press=on_press)
def poll_plate_status():
from time import sleep
from random import choice
messages = [
"It sure is tempting, eh?",
"Are you gonna do it?"
]
print("It'd be a shame if someone would drop this plate and scare grandpa!")
while not plate_dropped.is_set():
print(choice(messages))
sleep(0.5)
print("The plate has been dropped!")
def poll_grandpa_status():
from time import sleep
from random import choice
messages = [
"*zzzZZZzzz*",
"*Snoooreee*"
]
print("Grandpa is sound asleep.")
while not plate_dropped.is_set():
print(choice(messages))
sleep(0.5)
print("HUH!?")
plate_thread = Thread(target=poll_plate_status, daemon=True)
grandpa_thread = Thread(target=poll_grandpa_status, daemon=True)
plate_thread.start()
grandpa_thread.start()
listener.start()

##imports
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
##variable assigning
hshtag = int(0)
done = False
fire = False
def StopStart(self, interval=1):
hshtag = 0
self.interval = interval
while not done:
if keyboard.is_pressed('#'):
hshtag = hshtag + 1
if hshtag % 2 ==0:
fire = False
else:
fire = True
def NormalFire():
while not done:
print('NormalFire Runs')
time.sleep(1)
if fire:
print('*fires*')
else:
print('*does nothing*')
#t1 = Thread(target = StopStart, daemon=True)
t2 = Thread(target = NormalFire, daemon=True)
#t1.start()
t2.start()
while not done:
#time.sleep()
if keyboard.is_pressed('#'):
hshtag = hshtag + 1
time.sleep(0.1)
if hshtag % 2 ==0:
fire = False
print(fire)
else:
fire = True
print(fire)
I realised my problem was my ides of threading (im new to it) this achieves what I wanted thanks for all the help.

Related

How to stop playing a sound from playsound module [duplicate]

This question already has answers here:
How to stop audio with playsound module?
(7 answers)
Closed 12 months ago.
I'm making a timer program and I would like it to play a song at the end of the timer but I would also like to stop the sound when I press a button.
I've seen other posts that use the multiprocessing module (How to stop audio with playsound module?) to stop the audio but I am already using the threading module, so I was wondering if its possible to use that to stop the audio instead.
Edit: Matiis gave a solution different to what i was looking for but it still works perfectly. glory9211 also gave the solution i was looking for later on
from tkinter import *
from time import sleep
from threading import Thread
from threading import Event
import playsound
class App(Tk):
def __init__(self):
super().__init__()
self.title("Clock")
self.t_evt = Event()
self.frame2 = Frame(self)
self.timerStart = Button(self.frame2, text="Start", command=self.tt_Start)
self.timerStart.config(height=2, width=5)
self.timerStart.grid(row=1)
self.timerEnd = Button(self.frame2, text="End", command=self.timer_End)
self.timerEnd.config(height=2, width=5)
self.timerEnd.grid(row=2)
def tt_Start(self):
t = Thread(target=self.timer_Start)
t.setDaemon(True)
t.start()
def timer_Start(self):
self.t_evt.clear()
timer_seconds = int(self.timerS_Entry.get())
timer_minutes = int(self.timerM_Entry.get())
if timer_seconds > 59:
timer_seconds -= 60
timer_minutes += 1
while not self.t_evt.is_set():
print(f"Minutes: {timer_minutes}, Seconds: {timer_seconds}")
self.timerSeconds.config(text=timer_seconds)
self.timerMinutes.config(text=timer_minutes)
self.update()
time = (timer_minutes * 60) + timer_seconds
timer_seconds -= 1
sleep(1)
if time == 0:
break
if timer_seconds < 0:
timer_seconds = 59
timer_minutes -= 1
playsound.playsound('C:\\Users\\nonon\\mp3\\song.wav')
print("TIMER UP")
return
def timer_End(self):
self.t_evt.set()
here is some code for you to work off of, let me know if you need more.
Again, I would like to be able to stop playsound.playsound('C:\\Users\\nonon\\mp3\\song.wav') when I press the end button
Short Answer
You can use threading Events instead of threads. Or switch to multiprocessing and use p.terminate()
Long Answer
You cannot stop a python threading.Thread using any provided function. People achieving this using flags. good reads: Is there any way to kill a Thread?
i.e.
def thread_func():
while flag:
print("I'm running")
def run_button():
flag = True
t = threading.Thread(target=thread_func)
t.start()
def stop_button():
flag = False
# This will make the function exit
But in your case the playsound function isn't a looping function where you can sneak a flag to stop the function. You can imagine it to be indefinite sleep function i.e.
def play_sound():
time.sleep(1000000) # Replacing with playsound.playsound
So using the threading.Events() we can achieve this with this sample code
import random
import signal
import threading
import time
exit_event = threading.Event()
def bg_thread():
for i in range(1, 30):
print(f'{i} of 30 iterations...')
if exit_event.wait(timeout=random.random()):
break
print(f'{i} iterations completed before exiting.')
def signal_handler(signum, frame):
exit_event.set() # same as setting the flag False
signal.signal(signal.SIGINT, signal_handler)
th = threading.Thread(target=bg_thread)
th.start()
th.join()
This solution effectively gives you an "interruptible" sleep, because if the event is set while the thread is stuck in the middle of the call to wait() then the wait will return immediately.
Read the detailed example here: https://blog.miguelgrinberg.com/post/how-to-kill-a-python-thread

While loop in Threads

I am trying to use while loops inside threads for a bigger project. For simplicity I created an easier example to test it, but it doesn`t work.
My goal is to control the thread for the main function and when the variable go_thread_one is switched to False the thread should end. At the moment the second thread is not being used and only the first thread print its text.
How can I fix this error?
Below is the simplified version of my code:
import time
from threading import Thread
go_thread_one = True
def first_thread():
while go_thread_one:
print('Thread 1')
time.sleep(0.5)
def second_thread():
print('Thread 2')
if __name__ == "__main__":
t1 = Thread(target=first_thread())
t2 = Thread(target=second_thread())
t1.daemon = True
t2.daemon = True
t1.start()
t2.start()
time.sleep(2)
go_thread_one = False
print("end main Thread")
First of all, there is a problem in these lines:
t1 = Thread(target=first_thread())
t2 = Thread(target=second_thread())
You should pass a callable object to the Thread, but instead you call a function and pass its result. So you don't even create a t1, but go inside first_thread function and loop there forever.
To fix this, change Thread creation to:
t1 = Thread(target=first_thread)
t2 = Thread(target=second_thread)
Next, the
go_thread_one = False
will not give a desired effect – main thread will finish after time.sleep(2) even without this line.
To deal with it, you can add
t1.join()
t2.join()

Change variable after a specific amount of time python

I need to be able to switch the value of a Boolean after a specific amount of time whilst the rest of my code continues running as usual. What happens in the main part of the code is dependent on the value of the Bool.
Here's my attempt with goodguy's suggestion, but I still can't get it to work. 'playing' switches to True when I call the class, but doesn't switch back to False after 2 seconds so the tone only plays once. What am I doing wrong?
class TimedValue:
def __init__(self):
self._started_at = datetime.datetime.utcnow()
def __call__(self):
time_passed = datetime.datetime.utcnow() - self._started_at
if time_passed.total_seconds() > 2:
return False
return True
playing = False
while True:
trigger = randint(0,10) # random trigger that triggers sound
if trigger == 0 and playing == False:
#play a tone for 2 seconds whilst the random triggers continue running
#after the tone is over and another trigger happens, the tone should play again
thread.start_new_thread(play_tone, (200, 0.5, 2, fs, stream,))
value = TimedValue()
playing = value()
time.sleep(0.1)
Threading and multiprocessing sounds like an overkill for this case. Another possible way is to define something like callable class whose instance remembers time it was created at for measurements:
import datetime
class TimedValue:
def __init__(self):
self._started_at = datetime.datetime.utcnow()
def __call__(self):
time_passed = datetime.datetime.utcnow() - self._started_at
if time_passed.total_seconds() > XX:
return True
return False
value = TimedValue()
and when use value() as a callable in other parts of code
you can use the ThreadPool class from the module multiprocessing:
import time
myBool = False
def foo(b):
time.sleep(30) #time in seconds
return not b
from multiprocessing.pool import ThreadPool
pool = ThreadPool(processes=1)
result = pool.apply_async(foo,[myBool])
b = result.get()

Python: How to start/stop while True loop on Keyboard event

I am trying to write a little script that emulates mouse-clicking. It should start/stop when a combination of keys is pressed on the keyboard, so I figured every time this combination is pressed I would spawn or terminate a child process that just contains a while True loop and does some clicking. Now I can get the loop to start, but not to terminate. I tried creating a new process just for the HookManager, but i got the same results. Any help with this would be very appreciated
import time
import win32api
import win32con
import pythoncom
import pyHook
import multiprocessing
i=0
def click():
while True:
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
time.sleep(0.005)
def OnKeyboardEvent(event):
global i
if (event.Ascii == 4) and (i == 0):
i = 1
c = multiprocessing.Process(target=click())
c.start()
print("started")
elif (event.Ascii == 4) and (i == 1):
i = 0
c.terminate()
print("terminated")
return True
hm = pyHook.HookManager()
hm.KeyDown = OnKeyboardEvent
hm.HookKeyboard()
pythoncom.PumpMessages()
Obviously you dont want to use while True: statement, but something like this:
def worker():
while running:
#do the work
...
def terminateWorker():
running = false
...
running = true
startWorker()
terminateWorker()
Or you can use break in if statement checking loop stop flag.
def click():
while True:
if(stopNow):break
...
stopNow=false

python add time to a countdown already running

I want to have an app where if I click a button I add X amount of time to my running countdown timer.
I'm guessing I have to use threads for this but am not sure how to implement it..
Here is the code I have so far:
def countdown_controller(add_time):
end_it = False
def timer(time_this):
start = time.time()
lastprinted = 0
finish = start + time_this
while time.time() < finish:
now = int(time.time())
if now != lastprinted:
time_left = int(finish - now)
print time_left
lastprinted = now
if end_it == True:
now = finish
time.sleep(0.1)
# Check if the counter is running otherwise just add time.
try:
time_left
except NameError:
timer(add_time)
else:
if time_left == 0:
timer(add_time)
else:
add_this = time_left
end_it = True
while now != finish:
time.sleep(0.1)
timer(add_time + add_this)
Obviously this will not work, because every time I call countdown_controller(15) fx, it will start counting down for 15 seconds and if I click my button nothing happens until the timer is ended.
Help would be greatly appreciated.
I would say that there is a flaw in the design of the code, because your screen output blocks down the entire program doing nothing (time.sleep(0.1)).
Typically what you want to to do in these cases is having a main loop in your program that cycles through the various operations that make your program run. This guarantees a sensible distribution of system resources between the various tasks.
In your specific case, what you would like to have in your main loop is:
Check user input (has extra time been added?)
Update output of the countdown
Example implementation:
import time
import curses
# The timer class
class Timer():
def __init__(self):
self.target = time.time() + 5
def add_five(self):
self.target += 5
def get_left(self):
return int(self.target-time.time())
# The main program
t = Timer()
stdscr = curses.initscr()
stdscr.nodelay(True)
curses.noecho()
# This is the main loop done in curses, but you can implement it with
# a GUI toolkit or any other method you wish.
while True:
left = t.get_left()
if left <= 0:
break
stdscr.addstr(0, 0, 'Seconds left: %s ' % str(left).zfill(3))
c = stdscr.getch()
if c == ord('x') :
t.add_five()
# Final operations start here
stdscr.keypad(0)
curses.echo()
curses.endwin()
print '\nTime is up!\n'
The above program will increase the counter of 5 seconds if you press the x key (lowercase). Most of the code is boilerplate to use the curses module, but of course if you use PyGTK, PySide or any other graphical toolkit, it will be different.
EDIT: As a rule of thumb, in python you want to avoid threading as much as you can, both because it often (but not always) slows down programs (see "Global Interpreter Lock") and because it makes software harder to debug/maintain.
HTH!
I would probably have a Timer object with a finish attribute that I could simply add an int to. Have that timer running in another thread that you can then query for the current time remaining from your GUI.
class Timer(object):
def __init__(self, length):
self.finish = time.time() + length
def get_time(self):
return time.time() >= self.finish

Categories

Resources