[Python] How to interrupt a while loop using a button click with Jupyter Notebook

Discussão em 'Python' iniciado por Stack, Março 27, 2025.

  1. Stack

    Stack Membro Participativo

    I have a dashboard that gets node count, etc. from slurm and displays that as a button grid, and is refreshed every n seconds. I want the ability to click on any of the buttons and have something happen, e.g. get additional info, run a script, etc.

    The problem is that interactivity with the buttons in the grid does not work in a while loop.

    In the following test code, if event_loop = False, grid button clicks work as expected, but there is no update (no loop). If event_loop = True, the grid is updated, but the grid buttons no longer work.

    from ipywidgets import GridspecLayout, Button, Layout, Output
    from IPython.display import display, clear_output
    import time

    nodes = [1, 2, 3, 4, 5, 6]
    rows = 3
    cols = 3
    event_loop = False

    button_go = Button(
    description='Go',
    disabled=False,
    ayout={'width': '150px'}
    )

    output_command = Output()
    output_command.append_stdout('')

    @output_command.capture()
    def button_go_clicked(event):
    print(event)
    if event_loop:
    while event_loop:
    update_grid(rows, cols, nodes)
    time.sleep(10)
    else:
    update_grid(rows, cols, nodes)

    button_go.on_click(button_go_clicked)

    def create_dynamic_grid(rows, cols, nodes):
    grid = GridspecLayout(rows, cols)
    node = 0
    for i in range(rows):
    for j in range(cols):
    if node < len(nodes):
    if nodes[node]:
    button_grid = Button(description='Node_{}-{}'.format(i, j), button_style='primary', layout=Layout(width='auto', height='auto'))

    grid[i, j] = button_grid
    grid[i, j].on_click(button_grid_clicked)

    node += 1
    return grid

    output_grid = Output()

    def update_grid(rows, cols, nodes):
    with output_grid:
    output_grid.clear_output()
    new_grid = create_dynamic_grid(rows, cols, nodes)
    display(new_grid)

    @output_command.capture()
    def button_grid_clicked(btn):
    print(btn)

    display(button_go,
    output_grid,
    output_command)


    I need some way to 1. immediately interrupt/pause the loop when clicking a grid button, and 2. maintain/resume the loop refresh time of n seconds.

    After searching around on google, I found two interesting methods and integrated them. The first is from 'Kill a loop with Button Jupyter Notebook?'

    The second was from a google Generative AI from the prompt 'python immediately break from sleep', which generated the following:

    import time
    import threading

    def sleeper(seconds, event):
    while seconds > 0 and not event.is_set():
    time.sleep(min(seconds, 1))
    seconds -= 1
    if event.is_set():
    print("Sleep interrupted")
    else:
    print("Slept for", seconds, "seconds")

    def main():
    sleep_event = threading.Event()
    sleep_thread = threading.Thread(target=sleeper, args=(5, sleep_event))
    sleep_thread.start()

    input("Press Enter to interrupt sleep...\n")
    sleep_event.set()
    sleep_thread.join()
    print("Continuing main thread")

    if __name__ == "__main__":
    main()


    I have attempted to integrate these into my test code, my apologies if it's a bit of a jumble. The results are that when event_loop = True the grid is updated constantly, and if event_loop = False, no update.

    from ipywidgets import GridspecLayout, Button, Output, Layout
    from IPython.display import display, clear_output
    import asyncio, threading, time

    nodes = [1, 2, 3, 4, 5, 6]
    rows = 3
    cols = 3
    event_loop = False
    sleep_event = None
    sleep_thread = None

    button_go = Button(
    description='Go',
    disabled=False,
    layout={'width': '150px'}
    )

    output_command = Output()
    output_command.append_stdout('')

    async def function():
    while event_loop:
    print('function() event_loop: ', event_loop)
    print('done')

    def sleeper(seconds, event):
    while seconds > 0 and not event.is_set():
    print('sleeping: ', event)
    print('sleep_time: ', seconds)
    time.sleep(seconds)
    if event.is_set():
    print('sleep interrupted: ', event)

    def set_sleeper(sleep_time):
    global sleep_event
    global sleep_thread
    sleep_event = threading.Event()
    sleep_thread = threading.Thread(target=sleeper, args=(sleep_time, sleep_event))
    sleep_thread.start()
    return sleep_event

    @output_command.capture()
    def button_go_clicked(event):
    loop = asyncio.get_event_loop()
    loop.create_task(function())
    print('button_go_clicked event_loop: ', event_loop)
    se = set_sleeper(10)
    print('set_sleeper: ', se)
    if event_loop:
    while event_loop:
    update_grid(rows, cols, nodes) # grid updates constantly in free run
    # time.sleep(10) # <-- stop free run, but looking for the correct way to sleep here AND be able to interrupt on_click
    else:
    update_grid(rows, cols, nodes)

    button_go.on_click(button_go_clicked)

    def create_dynamic_grid(rows, cols, nodes):
    grid = GridspecLayout(rows, cols)
    node = 0
    for i in range(rows):
    for j in range(cols):
    if node < len(nodes):
    if nodes[node]:
    button_grid = Button(description='Node_{}-{}'.format(i, j), button_style='primary', layout=Layout(width='auto', height='auto'))

    grid[i, j] = button_grid
    grid[i, j].on_click(button_grid_clicked)

    node += 1
    return grid

    output_grid = Output()

    def update_grid(rows, cols, nodes):
    with output_grid:
    output_grid.clear_output()
    new_grid = create_dynamic_grid(rows, cols, nodes)
    display(new_grid)

    @output_command.capture()
    def button_grid_clicked(btn):
    print(btn)
    if event_loop:
    global sleep_event
    global sleep_thread
    print('interrupting sleep...\n')
    sleep_event.set()
    sleep_thread.join()
    print('continuing thread')

    display(button_go,
    output_grid,
    output_command)


    My anticipation was that set_sleeper(10) would act as the refresh time (10 seconds).

    Any suggestions on how I can make this work as desired?

    Regards

    Continue reading...

Compartilhe esta Página