Reputation: 1168
I want to:
From How to kill a while loop with a keystroke? I have taken the example to interrupt using Keyboard Interrupt, This works, but i would like to use a button.
EXAMPLE WITH KEYBOARD INTERRUPT
weights = []
times = []
#open port
ser = serial.Serial('COM3', 9600)
try:
while True: # read infinite loop
#DO STUFF
line = ser.readline() # read a byte string
if line:
weight_ = float(line.decode()) # convert the byte string to a unicode string
time_ = time.time()
weights.append(weight_)
times.append(time_)
print (weight_)
#STOP it by keyboard interup and continue with program
except KeyboardInterrupt:
pass
#Continue with plotting
However I would like to do it with a displayed button (easier for people to use). I have tried making a button (in Jupiter Notebook) that when pressed break_cicle=False, but the loop doesn't break when button pressed:
#make a button for stopping the while loop
button = widgets.Button(description="STOP!") #STOP WHEN THIS BUTTON IS PRESSED
output = widgets.Output()
display(button, output)
break_cicle=True
def on_button_clicked(b):
with output:
break_cicle = False # Change break_cicle to False
print(break_cicle)
ser.close()
button.on_click(on_button_clicked)
ser = serial.Serial('COM3', 9600)
try:
while break_cicle:
print (break_cicle)
line = ser.readline() # read a byte string
if line:
weight_ = float(line.decode()) # convert the byte string to a unicode string
time_ = time.time()
weights.append(weight_)
times.append(time_)
print (weight_)
except :
pass
ser.close()
EXAMPLE WITH GLOBAL NOT WORKING
from IPython.display import display
import ipywidgets as widgets
button = widgets.Button(description="STOP!") #STOP WHEN THIS BUTTON IS PRESSED
output = widgets.Output()
display(button, output)
break_cicle=True
def on_button_clicked():
global break_cicle #added global
with output:
break_cicle = False # Change break_cicle to False
print ("Button pressed inside break_cicle", break_cicle)
button.on_click(on_button_clicked)
try:
while break_cicle:
print ("While loop break_cicle:", break_cicle)
time.sleep(1)
except :
pass
print ("done")
Despite me pressing the button a few times,from the following image you can see that it never prints "Button pressed inside break_cicle".
Upvotes: 4
Views: 2863
Reputation: 142641
I think problem is like in all Python scripts with long-running code - it runs all code in one thread and when it runs while True
loop (long-running code) then it can't run other functions at the same time.
You may have to run your function in separated thread - and then main thread can execute on_button_clicked
This version works for me:
from IPython.display import display
import ipywidgets as widgets
import time
import threading
button = widgets.Button(description="STOP!")
output = widgets.Output()
display(button, output)
break_cicle = True
def on_button_clicked(event):
global break_cicle
break_cicle = False
print("Button pressed: break_cicle:", break_cicle)
button.on_click(on_button_clicked)
def function():
while break_cicle:
print("While loop: break_cicle:", break_cicle)
time.sleep(1)
print("Done")
threading.Thread(target=function).start()
Maybe Jupyter has some other method for this problem - ie. when you write functions with async
then you can use asyncio.sleep()
which lets Python to run other function when this function is sleeping.
EDIT:
Digging in internet (using Google) I found post on Jyputer forum
Interactive widgets while executing long-running cell - JupyterLab - Jupyter Community Forum
and there is link to module jupyter-ui-poll which shows similar example (while
-loop + Button
) and it uses events
for this. When function pull()
is executed (in every loop) then Jupyter can send events to widgets and it has time to execute on_click()
.
import time
from ipywidgets import Button
from jupyter_ui_poll import ui_events
# Set up simple GUI, button with on_click callback
# that sets ui_done=True and changes button text
ui_done = False
def on_click(btn):
global ui_done
ui_done = True
btn.description = '👍'
btn = Button(description='Click Me')
btn.on_click(on_click)
display(btn)
# Wait for user to press the button
with ui_events() as poll:
while ui_done is False:
poll(10) # React to UI events (upto 10 at a time)
print('.', end='')
time.sleep(0.1)
print('done')
In source code I can see it uses asyncio
for this.
EDIT:
Version with multiprocessing
Processes don't share variables so it needs Queue
to send information from one process to another.
Example sends message from button
to function
. If you would like to send message from function
to button
then better use second queue.
from IPython.display import display
import ipywidgets as widgets
import time
import multiprocessing
button = widgets.Button(description="STOP!")
output = widgets.Output()
display(button, output)
queue = multiprocessing.Queue()
def on_button_clicked(event):
queue.put('stop')
print("Button pressed")
button.on_click(on_button_clicked)
def function(queue):
while True:
print("While loop")
time.sleep(1)
if not queue.empty():
msg = queue.get()
if msg == 'stop':
break
#if msg == 'other text':
# ...other code...
print("Done")
multiprocessing.Process(target=function, args=(queue,)).start()
or more similar to previous
def function(queue):
break_cicle = True
while break_cicle:
print("While loop: break_cicle:", break_cicle)
time.sleep(1)
if (not queue.empty()) and (queue.get() == 'stop'):
break_cicle = False
print("Done")
EDIT:
Version with asyncio
Jupyter already is running asynio event loop
and I add async function
to this loop. And function uses await
functions like asyncio.sleep
so asynio event loop
has time to run other functions - but if function could run only standard (not async) functions then it wouldn't work.
from IPython.display import display
import ipywidgets as widgets
import asyncio
button = widgets.Button(description="STOP!")
output = widgets.Output()
display(button, output)
break_cicle = True
def on_button_clicked(event):
global break_cicle
break_cicle = False
print("Button pressed: break_cicle:", break_cicle)
button.on_click(on_button_clicked)
async def function(): # it has to be `async`
while break_cicle:
print("While loop: break_cicle:", break_cicle)
await asyncio.sleep(1) # it needs some `await` functions
print("Done")
loop = asyncio.get_event_loop()
t = loop.create_task(function()) # assign to variable if you don't want to see `<Task ...>` in output
Upvotes: 9