[Python-ideas] How the heck does async/await work in Python 3.5
Terry Reedy
tjreedy at udel.edu
Sat Feb 27 03:08:53 EST 2016
On 2/26/2016 5:12 PM, Maxime S wrote:
>
> 2016-02-25 17:47 GMT+01:00 Guido van Rossum
> <guido at python.org
> <mailto:guido at python.org>>:
>
> When you're implementing this API on top of tkinter, you'll probably
> find that you'll have to use tkinter's way of sleeping anyways, so the
> implementation of waiting in BaseEventLoop using a selector is not
> useful for this scenario.
>
> There are probably some possible refactorings in the asyncio package
> to help you reuse a little more code, but all in all I still think it
> would be very useful to have an asyncio loop integrated with Tkinter.
> (Of course Tkinter does support network I/O, so it would be possible
> to integrate with that, too. Or some hybrid where you somehow figure
> out how to wait using a Selector *or* tkinter events in the same
> loop.)
>
>
> It is actually quite easy to implement an asyncio loop over tkinter once
> you realise that tkapp.dooneevent() is very similar to poll(), and
It took me awhile to understand what you mean by 'tkapp'. Instances of
tkinter.Tk get an undocumented .tk attribute that is an instance of the
undocumented and hidden _tkinter class that is called 'tkapp' in printed
representations. In other words, after
import tkinter as tk
root = tk.Tk()
tkapp = root.tk
tkapp has a dooneevent method. I found the tcl doc for it at
https://www.tcl.tk/man/tcl/TclLib/DoOneEvent.htm
Does calling it with DONT_WAIT "TCL_DONT_WAIT - Do not sleep: process
only events that are ready at the time of the call." differ from calling
root.update?
def update(self):
"""Enter event loop until all pending events have been
processed by Tcl."""
self.tk.call('update')
If so, how? I interpret 'pending events' as 'events that are ready'.
To add to the confusion, 2.7 aliased the tkapp methods (and attributes?)
as Tkinter.tkinter functions, where Tkinter.tkinter was the C-coded
_tkinter module. At least some people used the alieases. In 3.0, the
aliases were removed, and when 'Tkinter' became 'tkinter', 'tkinter
because '_tkinter'. (You may know all this, but I want it recorded and
possibly added to the docs someday.)
> tkapp.createfilehandler() is very similar to register() (which is not
> that surprising since tcl use poll() internally if available).
As you note below, tkapp.createfilehandler is unix-only and does not
exist on Windows in 2.x or 3.x. In 2.7, however, the
Tkinter.tkinter.createfilehandle alias did exist on Windows, but with
with a value of None.
> Thus it is possible to create a tk selector and reuse all the code in
> SelectorEventLoop. Unfortunately createfilehandler() is only available
> on UNIX, so some form of threading is probably innevitable on Windows.
>
> class TkSelector(selectors._BaseSelectorImpl):
> """Selector based on the Tk event loop."""
>
> def __init__(self, app):
> super().__init__()
> self.app = app
> self.ready = []
> self.is_timeout = False
> self.after_key = None
> def _file_cb(self, fileobj, mask):
> fd = self._fileobj_lookup(fileobj)
> self.ready.append((fd, mask))
> def _timeout_cb(self):
> self.is_timeout = True
> def _reset_state(self):
> del self.ready[:]
> self.is_timeout = False
> if self.after_key:
> self.app.after_cancel(self.after_key)
> self.after_key = None
>
> def register(self, fileobj, events, data=None):
> key = super().register(fileobj, events, data)
> flags = 0
> if events & EVENT_READ:
> flags |= tkinter.READABLE
> if events & EVENT_WRITE:
> flags |= tkinter.WRITABLE
> self.app.createfilehandler(fileobj, flags, self._file_cb)
> return key
>
> def unregister(self, fileobj):
> key = super().unregister(fileobj)
> self.app.deletefilehandler(fileobj)
> return key
>
> def select(self, timeout=None):
> if timeout is None:
> pass
> elif timeout <= 0:
> while self.app.dooneevent(_tkinter.DONT_WAIT):
> pass
> self.is_timeout = True
> else:
> self.after_key = self.app.after(math.ceil(timeout*1000),
> self._timeout_cb)
> while not (self.is_timeout or self.ready):
> self.app.dooneevent()
> ret = []
> for fd, mask in self.ready:
> events = 0
> if mask & tkinter.WRITABLE:
> events |= EVENT_WRITE
> if mask & tkinter.READABLE:
> events |= EVENT_READ
>
> key = self._key_from_fd(fd)
> if key:
> ret.append((key, events & key.events))
> self._reset_state()
> return ret
>
> class TkEventLoop(asyncio.SelectorEventLoop):
> """Asyncio-compatible tkinter event loop."""
> def __init__(self, app):
> selector = TkSelector(app)
> super().__init__(selector=selector)
I am saving this for future reference.
--
Terry Jan Reedy
More information about the Python-ideas
mailing list