I am trying to access the current slider value from a function, but it seems that it can only get the value that the slider had when the function was called.
Is there a way to do this without using multi threading?
Code example:
import ipywidgets as widgets
import time
slider = widgets.IntSlider(
value=5,
min=0,
max=10,
step=1,
description='Test:',
disabled=False,
continuous_update=False,
orientation='horizontal',
readout=True,
readout_format='d'
)
def test(slider, output):
i = 0
while slider.value != 10:
i = i+1
time.sleep(0.1)
with output:
output.clear_output()
print('test ' + str(slider.value) + ' - ' + str(i))
output = widgets.Output()
display(slider, output)
test(slider, output)
Inside a modern ipython/ipykernel, you’ll pretty much always be inside an asyncio-compatible event loop. If you block, though, then things stop flowing.
Output
is kind of an implementation nightmare, with each frontend being slightly different. However, display(..., display_id=True).update
works great.
So:
import ipywidgets as widgets
import asyncio
slider = widgets.IntSlider(
value=5,
min=0,
max=10,
step=1,
description='Test:',
disabled=False,
continuous_update=False,
orientation='horizontal',
readout=True,
readout_format='d'
)
async def test(slider, output):
i = 0
while slider.value != 10:
i = i+1
await asyncio.sleep(0.1)
output.update('test ' + str(slider.value) + ' - ' + str(i))
output = display("tbd", display_id=True)
display(slider)
asyncio.create_task(test(slider, output));
Related: Threading with Matplotlib and ipywidgets - #2 by bollwyvl
2 Likes
Thank you! Great solution!
It was exactly the type of solution I was looking for.
I will suggest that they add this as an example to the manual.
Thank you.
I had success with this.
import ipywidgets as widgets
import asyncio
slider = widgets.IntSlider()
async def job(slider, output):
for i in range(100): # 10 sec job
await asyncio.sleep(0.1)
output.update('job ' + str(slider.value))
output = display("tbd", display_id=True)
display(slider)
asyncio.create_task(job(slider, output))
I want to retain the right to manually cancel the job mid-way.
But I failed this.
import ipywidgets as widgets
import asyncio
slider = widgets.IntSlider()
async def job(slider, output):
for i in range(100): # 10 sec job
await asyncio.sleep(0.1)
output.update('job ' + str(slider.value))
output = display("tbd", display_id=True)
display(slider)
await asyncio.gather(
asyncio.create_task(job(slider, output))
)
How can I manually cancel a job mid-way?
1 Like
I cannot retain the right to manually cancel the job midway (using Jupyter’s standard UI)… (only during the first call)
import ipywidgets as widgets
import asyncio, time
output = display("tbd", display_id=True)
slider = widgets.IntSlider()
display(slider)
def callback(count):
output.update(slider.value)
time.sleep(1)
if count < 10:
asyncio.get_running_loop().call_soon_threadsafe(callback, count+1)
callback(0)
asyncio.create_task
returns an asyncio.Task
which has a .cancel()
method.
Calling that that will raise a asyncio.CancelledError
, which can be caught and handled. Applying this pattern to the previous example:
import ipywidgets as widgets
import asyncio
slider = widgets.IntSlider(
value=5,
min=0,
max=10,
step=1,
description='Test:',
disabled=False,
continuous_update=False,
orientation='horizontal',
readout=True,
readout_format='d'
)
button = widgets.Button(description="cancel")
task = None
def cancel(*args):
task.cancel()
button.on_click(cancel)
async def test(slider, output):
i = 0
try:
while slider.value != 10:
i = i+1
await asyncio.sleep(0.1)
output.update('test ' + str(slider.value) + ' - ' + str(i))
except asyncio.CancelledError:
output.update('CANCELLED ' + str(slider.value) + ' - ' + str(i))
return
output = display("tbd", display_id=True)
display(slider, button)
task = asyncio.create_task(test(slider, output));
1 Like
call_soon_threadsafe
time.sleep
In general, asyncio
primitives should only be mixed with threading
(or time.sleep
) with great care, ideally via some primitive like collections.deque. The call_soon_threadsafe
won’t reliably protect from all the ways things can Go Wrong. The anyio also provides some nice wrappers for things one would be tempted to use threading for (processes, file IO), and is used in a number of core jupyter projects already.
1 Like
asyncio.create_task returns an asyncio.Task which has a .cancel() method.
Thank you! bollwyvl
worked very well !!
But perfect control is difficult, like a kite connected by a weak string…
(If I run the cell many times while the job is still running…)
It is necessary to block so that multiple jobs do not run at the same time
Little context is given, but keep in mind: if a “user-space” activity blocks the IO loop, shared with the underlying kernel machinery, then sending and receiving of non-interrupt kernel messages (like widget updates) will cease, leaving the UI unresponsive.
Thank you
I understand.
By the way…
I was running the code and noticed that the screen display didn’t change unless I moved the slider.
I couldn’t see the logs that the jobs printed in the background.