2

I want to start an while loop that can only be cancelled when another socket command breaks that loop. I've tried using asyncio but the server doesn't accept incoming messages until the while loop ends/breaks.

Here is a simplified version of my code that only runs for 5 seconds. After 2 seconds it should cancel the whileLoop function using the "endLoop" message

server.py

# python 3.7+
import socket
import asyncio

class SocketHandler():
    def __init__(self, conn):
        self.conn = conn
        self.run_loop = False

    async def recv_loop(self):
        try:
            print('client connected')
            while True:
                cmd = self.conn.recv(1024)  # receive data from client
                cmd = cmd.decode()
                print(cmd)
                if len(cmd) == 0:
                    break
                elif cmd == "startLoop":
                    self.run_loop = True
                    task2 = asyncio.create_task(self.whileLoop())
                    task3 = asyncio.create_task(test_counter())
                    await task2
                    await task3
                elif cmd == "endLoop":
                    self.run_loop = False
        finally:
            self.conn.close()

    async def whileLoop(self):
        count = 0
        while self.run_loop:
            print('self.run_loop: ' + str(self.run_loop))
            # the below line should allow for other processes to run however
            # 'cmd = self.conn.recv(1024)' only runs after the while loop breaks
            await asyncio.sleep(1)

            # break the loop manually after 5 seconds
            count += 1
            if count > 5:
                break

async def test_counter():
# just a dummy async function to see if the whileLoop func
# allows other programs to run
    for k in range(5):
        print(str(k))
        await asyncio.sleep(1)

async def main():
    # this is the main asyncio loop that initializes the socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # sock.setblocking(False)

    # Bind the socket to the address given on the command line
    server_address = ("127.0.0.1", 22000)
    print('starting up on %s port %s' % server_address)
    sock.bind(server_address)
    sock.listen(1)
    while True:
        print('waiting for a connection')
        connection, client_address = sock.accept()
        socketHandler = SocketHandler(connection)
        task1 = asyncio.create_task(socketHandler.recv_loop())  # create recv_loop as a new asyncio task
        await task1

if __name__ == '__main__':
    asyncio.run(main())

client.py

import time
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
sock.connect(('127.0.0.1', 22000))

sock.sendall(b'startLoop')
time.sleep(2)
sock.sendall(b'endLoop')

sock.close()

Expected result:

client connected
startLoop
self.run_loop: True
0
self.run_loop: True
1
self.run_loop: True
2
endLoop
3
4

Actual results:

client connected
startLoop
self.run_loop: True
0
self.run_loop: True
1
self.run_loop: True
2
self.run_loop: True
3
self.run_loop: True
4
self.run_loop: True
endLoop
4
  • 1
    The extensions should be *.py not *.js... Also -- you don't install asyncio (unless you're using python 3.3 which you're not), it's a stdlib. Commented Nov 3, 2021 at 15:02
  • Simple mistake... It's a react native project that connects to a raspberry pi. Most of the code is .js and .ts and I just wanted to include as much needed info as possible. Also why is it getting downvoted? stackoverflow.com/help/privileges/vote-down >Downvote questions that don't show any research effort or don't contain enough information to be clear and answerable. These questions may also need to be closed. I literally put in the effort to create 2 little copy pastable scripts in its most basic form my main project, it contains more than enough info and I edited the .py part? Commented Nov 4, 2021 at 8:03
  • This looks half asyncio and unlike anything asyncio. Try stackoverflow.com/a/48507121/6242321 which shows both unintended and intended use for asyncio. Take care to read the part of the post which says NOT like this. Commented Nov 4, 2021 at 18:39
  • @Lilliën Don't stress over the downvote - it's just one person's opinion, and it's likely that they were reacting (over-reacting?) to the .js problem. Commented Nov 4, 2021 at 21:16

1 Answer 1

0

It might be useful to understand why the program doesn't work. This line:

await task2

means, in English, "Until task2 is finished, suspend the execution of this task right here." Since that statement appears inside your while loop, the next cycle through your loop won't occur until task2 is finished. That will be in 5 seconds. You're frozen until then, so you don't reach the logic that detects the receipt of the "endLoop" command.

If you remove both of these lines:

await task2
await task3

then you will get back to the top of your while loop. That's a good thing, but now you will encounter the next problem: the call to recv() is blocking. You won't go to the next line until some more data arrives on the socket. That's OK but recv() isn't a async function, so your event loop will be blocked as well. Task2 and task3 can't run if the loop is blocked.

The problem with your program is that you are trying to use asyncio with blocking socket calls. The asyncio module contains tools for solving this, but you aren't using them. I can't see a simple fix for your program since your basic approach is at fault.

I suggest looking at the asyncio support for sockets, as well as the Python HOW TO for Socket Programming.

Sign up to request clarification or add additional context in comments.

3 Comments

That is where "await asyncio.sleep(1)" should come in and allow other stuff to process. You are completely right when saying recv() is blocking and it doesn't seem to want to work with asyncio (quite annoying). I found this: stackoverflow.com/questions/67006225/… which allows me to check for other pending messages which means I can just drop asyncio completely.
The asyncio module has a different implementation of sockets that is designed to work in async programs. But if you would rather get annoyed, that's your call.
asyncio.sleep() only allows other stuff to process if the event loop is not blocked.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.