[Python-Dev] Using async/await in place of yield expression

Terry Reedy tjreedy at udel.edu
Tue Nov 28 00:56:48 EST 2017


On 11/27/2017 5:05 PM, Guido van Rossum wrote:
> On Mon, Nov 27, 2017 at 1:58 PM, Greg Ewing <greg.ewing at canterbury.ac.nz 
> <mailto:greg.ewing at canterbury.ac.nz>> wrote:
> 
>     Guido van Rossum wrote:
>         The source for sleep() isn't very helpful -- e.g. @coroutine is
>         mostly a backwards compatibility thing.
> 
>     So how are you supposed to write that *without* using @coroutine?
> 
> A simplified version using async def/await:

---
> async def sleep(delay):
>      f = Future()

This must be asyncio.Future as (by experiment) a 
concurrent.futures.Future cannot be awaited.  The a.F doc could add this 
as a difference.  Future needs an argument for the keyword-only loop 
parameter; as I remember, the default None gets replaced by the default 
asyncio loop.

>      get_event_loop().call_later(delay, f.set_result)

A result is needed, such as None or delay, to pass to f.set_result.

>      await f

I gather that
1. the value of the expression is the result set on the future, which 
would normally be needed, though not here;
2. the purpose of 'await f' here is simply to block exit from the 
coroutine, without blocking the loop, so that users of 'await sleep(n)' 
will actually pause (without blocking other code).

Since a coroutine must be awaited, and not just called, and await can 
only be used in a coroutine, there seems to be a problem of where to 
start.  The asyncio answer, in the PEP, is to wrap a coroutine call in a 
Task, which is, as I remember, done by the loop run methods.

Based on the notes above, and adding some prints, I got this to run:

---
import asyncio
import time

loop = asyncio.get_event_loop()

async def sleep(delay):
     f = asyncio.Future(loop=loop)
     loop.call_later(delay, f.set_result, delay)
     print('start')
     start = time.perf_counter()
     d = await f
     stop = time.perf_counter()
     print(f'requested sleep = {d}; actual = {stop-start:f}')

loop.run_until_complete(sleep(1))
---
This produces:
start
requested sleep = 1; actual = .9--- [usually < 1]

---
Now, the question I've had since async and await were introduced, is how 
to drive async statements with tkinter.  With the help of the working 
example above, I make a start.

---
from asyncio import Future, Task
import tkinter as tk
import time


class ATk(tk.Tk):
     "Enable tkinter program to use async def, etc, and await sleep."

     def __init__(self):
         super().__init__()

     def task(self, coro):
         "Connect async def coroutine to tk loop."
         Task(coro, loop=self)

     def get_debug(self):
         "Internal method required by Future."
         print('debug')
         return False

     def call_soon(self, callback, *args):
         "Internal method required by Task and Future."
         # TaskStep/Wakeup/MethWrapper has no .__name__ attribute.
         # Tk.after requires callbacks to have one (bug, I think).
         print('soon', callback, *args, hasattr(callback, '__name__'))
         def wrap2(): callback(*args)
         return self.after(0, wrap2)


root = ATk()

async def sleep(delay):
     f = Future(loop=root)
     def cb():
         print('cb called')
         f.set_result(delay)
     root.after(int(delay*1000), cb)
     print('start')
     start = time.perf_counter()
     d = await f
     stop = time.perf_counter()
     print(f'requested sleep = {d}; actual = {stop-start:f}')

root.task(sleep(1))
root.mainloop()
---

Output:
debug
soon <TaskStepMethWrapper object at 0x000001AE982E1748> False
debug
start
cb called
soon <TaskWakeupMethWrapper object at 0x000001AE988E04C8> <Future 
finished result=1> False
requested sleep = 1; actual = 1.01--- [always about 1.01]

Replacing the last two lines with

async def myloop(seconds):
     while True:
         print(f'*** {seconds} ***')
         await sleep(seconds)

root.task(myloop(1.2))
root.task(myloop(.77))
root.mainloop()

prints interleaved 1.2 and .77 lines.

I will next work on animating tk widgets in the loops.

-- 
Terry Jan Reedy




More information about the Python-Dev mailing list