0

I am creating a button that runs a job when clicked using ipywidgets all inside of a Jupyter Notebook. This job can take some long amount of time, so I would like to also give the user the ability to stop the job.

I've created a minimally reproducible example that runs for only 10 seconds. All of the following is run from a Jupyter Notebook cell:

import ipywidgets as widgets
from IPython.display import display
from time import sleep

button = widgets.Button(description='run job')
output = widgets.Output()


def abort(event):
    with output:
        print('abort!')

    
def run_job(event):
    with output:
        print('running job')
        button.description='abort'
        button.on_click(run_job, remove=True)
        button.on_click(abort)
        sleep(10)
        print('job complete!')

button.on_click(run_job)
display(button, output)

If the user clicks 'run job', then waits 2 seconds, then clicks 'abort'. The behavior is:

running job
job complete!
abort!

Which implies the 'abort' event fires after the job is already complete. I would like the print sequence to be:

running job
abort!
job complete!

If the abort can fire immediately when clicked, then I have a way to actually stop the job inside of my class objects.

How can I get the abort event to run immediately when clicked from a Jupyter Notebook?

lane
  • 766
  • 5
  • 20
  • Meant to reply earlier. Just a quick note since I'm heading out. What you describe needs to involve multiple processes/threads or possibly asyncio, see [here](https://discourse.jupyter.org/t/threading-with-matplotlib-and-ipywidgets/14674/2?u=fomightez) for some inspiration. I'll probably delete this soon if I think of how to address your case later. – Wayne Dec 22 '22 at 22:16
  • Thanks for sharing. I will look into this. And also please post a solution if you have time I am happy to give credit where credit is due. – lane Dec 23 '22 at 00:31
  • Awesome that you got it! I should have also pointed at [this thorough answer with multiple approaches demonstrated with code here under 'Kill a loop with Button Jupyter Notebook?](https://stackoverflow.com/a/71223447/8508004). I'm still adding it as the additional pointers may help someone else who ends up here. – Wayne Dec 23 '22 at 15:23
  • Thanks I will take a look at that as well. The more approaches the better, I am having some success in my notebook but looking for a better way to accomplish this in another area. – lane Dec 23 '22 at 15:27

1 Answers1

1

After being pointed in the right direction by Wayne in the comment section, asyncio seems to provide the desired capability within Jupyter Notebooks and ipywidgets. In the following discourse thread bollwyvl posts a solution for how to use asyncio to accomplish a similar pattern https://discourse.jupyter.org/t/threading-with-matplotlib-and-ipywidgets/14674/2?u=fomightez

import ipywidgets as widgets
from IPython.display import display
from time import sleep
import asyncio

button = widgets.Button(description='run job')
tasks = dict()

def abort(event):
    print('abort!')
    task = tasks.pop("run_job", None)
    if task:
        task.cancel()

async def run_job():
    print('running job')
    button.description='abort'
    button.on_click(run_job, remove=True)
    button.on_click(abort)
    await asyncio.sleep(10)
    print('job complete!')

def start_job(event):
    button.description='abort'
    button.on_click(start_job, remove=True)
    button.on_click(abort)
    tasks['run_job'] = asyncio.get_event_loop().create_task(run_job())
    print('started job')

button.on_click(start_job)
display(button)

When 'start' button is clicked, then 'abort' button. The following output is produced:

started job
running job
abort!
lane
  • 766
  • 5
  • 20