[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