2016-02-25 17:47 GMT+01:00 Guido van Rossum <guido@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 tkapp.createfilehandler() is very similar to register() (which is not that surprising since tcl use poll() internally if available).

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)