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?
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!