Asyncio, Tk, and matplotlib

Hi everyone,

I?m new to both asynchronous programming and to GUI programming so please forgive any newbie mistakes in the below code.

I?m developing a simple GUI app that takes some parameters from the user, then when the user clicks a button, launches some long-running task, *which includes making and saving a matplotlib plot*. When using the standard Tk `app.mainloop()`, this causes the application to become non-responsive while the long computation is running.

So I figured I?d use this newfangled asyncio thnigy that everyone is talking about. =P I settled on modelling it after Guido?s own tkcrawl.py [1]_, which periodically ?manually" calls tkinter?s `update()` within the asyncio event loop. Unfortunately, when matplotlib is invoked in the asynchronous task, it crashes tkinter with the error:

RuntimeError: main thread is not in main loop

Apparently calling tk functions from anything other than the main thread is a big no-no.

But, this is where I?m stuck: how do I asynchronously launch a long-running task that includes matplotlib plotting? Any ideas about how I can tweak my design so my app is responsive but tasks include making big plots?

I?ve included sample code below. By swapping out ?fasync? for ?fsync?, you too can experience the frustration of a beach-balled or greyed out app ? but at least the app works! :wink:

Thanks!

Juan.

.. [1]?https://bugs.python.org/file43873/tkcrawl.py

Minimal code:

import asyncio
import matplotlib
matplotlib.use('TkAgg')
import tkinter as tk
from tkinter import ttk
from skimage._shared._tempfile import temporary_file
import numpy as np

@asyncio.coroutine
def _async(func, *args):
? ? loop = asyncio.get_event_loop()
? ? return (yield from loop.run_in_executor(None, func, *args))

STANDARD_MARGIN = (3, 3, 12, 12)

def long_computation():
? ? import time
? ? time.sleep(4)
? ? import matplotlib.pyplot as plt
? ? fig, ax = plt.subplots()
? ? ax.imshow(np.random.rand(500, 500))
? ? with temporary_file(suffix='.png') as fname:
? ? ? ? fig.savefig(fname)

class MainWindow(tk.Tk):
? ? def __init__(self):
? ? ? ? super().__init__()
? ? ? ? self.title('I gonna die')
? ? ? ? main = ttk.Frame(master=self, padding=STANDARD_MARGIN)
? ? ? ? main.grid(row=0, column=0, sticky='nsew')
? ? ? ? fsync = long_computation
? ? ? ? fasync = lambda: asyncio.ensure_future(_async(long_computation))
? ? ? ? button = ttk.Button(master=main, padding=STANDARD_MARGIN,
? ? ? ? ? ? ? ? ? ? ? ? ? ? text='Run stuff',
? ? ? ? ? ? ? ? ? ? ? ? ? ? command=fasync)
? ? ? ? button.grid(row=0, column=0)
? ? ? ? main.pack()

def tk_update(loop, app):
? ? try:
? ? ? ? app.update()
? ? except tk.TclError as e:
? ? ? ? loop.stop()
? ? ? ? return
? ? loop.call_later(.01, tk_update, loop, app)

def main():
? ? loop = asyncio.get_event_loop()
? ? app = MainWindow()
? ? #app.mainloop()
? ? tk_update(loop, app)
? ? loop.run_forever()

if __name__ == '__main__':
? ? main()

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/matplotlib-users/attachments/20170223/0c9d7d36/attachment.html>