Reputation: 189
Is it possible to update the state of an object (i.e. value of member variable) whilst awaiting asyncio.sleep?
For example, let’s say you have a server sending a count that increments every second and the client reads and stores the current count in a member variable. If every mod 100 it asyncio.sleeps for 10 seconds, can the value of the counter member variable change before and after the sleep? So for example sleeps at 100 and resumes at 110 or still 100?
code example:
server.py
import asyncio
async def send_counter(reader, writer):
cnt = 0
while True:
print(f"send: {cnt!r}")
# encode counter as little endian and send
writer.write((cnt).to_bytes(4, "little"))
await writer.drain()
cnt += 1
# pace the sends
await asyncio.sleep(1)
async def main():
server = await asyncio.start_server(send_counter, "127.0.0.1", 5555)
async with server:
await server.serve_forever()
asyncio.run(main())
client.py
import asyncio
class MyClass:
def __init__(self):
self._cnt = 0
@property
def count(self):
return self._cnt
@count.setter
def count(self, x):
self._cnt = x
async def do_something_then_sleep(self):
before = self._cnt
# do something here
print(f"sleeping for 10 seconds - count = {self._cnt}")
await asyncio.sleep(10)
# ideally self._cnt would reflect the latest count sent by the server
if before != self._cnt:
print(f"before and after different {before} != {self._cnt}")
async def tcp_echo_client():
reader, _ = await asyncio.open_connection("127.0.0.1", 5555)
o = MyClass()
while True:
data = await reader.read(4)
o.count = int(int.from_bytes(data, "little"))
print(f"Received: {o.count!r}")
if (o.count % 10) == 9:
await o.do_something_then_sleep()
asyncio.run(tcp_echo_client())
In the example above I believe the problem is that whilst awaiting o.do_something_then_sleep
I stop reading the socket, which makes sense. Ideally I would continue reading the socket during the sleep, causing the before and after different
message to appear.
Can this be done on a single thread avoiding introducing threads, run_in_executor, new event loops and locks? Perhaps there is a mechanism in asyncio which can be used to automatically read the socket and trigger a callback with the data, avoiding the explicit read?
Upvotes: -1
Views: 5874
Reputation: 15309
Yes! AP (Asynchronous Programming) is essentially "concurrency in a single thread". This means that while one coroutine is sleeping, other coroutines can run.
Note however that unlike with multithreading, no other coroutine can run in between two statements if you do not sleep (call await
), so in my example below the variable cannot be changed externally between the two print statements.
import asyncio
import random
counter = 0
async def every_tenth_of_a_second():
global counter
while True:
counter += 1
await asyncio.sleep(.2 * random.random())
async def every_second():
global counter
while True:
print('counter was', counter)
counter += 1
print('counter is now', counter)
await asyncio.sleep(1)
async def async_main():
await asyncio.wait([
asyncio.create_task(every_tenth_of_a_second()),
asyncio.create_task(every_second()),
])
asyncio.run(async_main())
This printed:
counter was 1
counter is now 2
counter was 11
counter is now 12
counter was 20
counter is now 21
counter was 34
counter is now 35
counter was 44
counter is now 45
counter was 56
counter is now 57
counter was 69
counter is now 70
counter was 82
counter is now 83
I recommend my blog post on AP for further explanation! (:
Upvotes: 1