Is it possible to get the current value of a widget slider from a function without using multithreading?

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.