How to do a loop without holding up the system

Viewed 445

I'm trying to do a command that will activate a cooldown timer in python, but I'm having issues. It's locking up the entire program to no longer allow any other inputs until cooldown finishes. I want to be able to enter more commands WHILE the loop is waitting for X amount of seconds to pass.

Example:

import asyncio
import time
count = 0
cool = 0

async def main():
    global count
    while True:
        userinput = input("enter command")
        if userinput == "test":
            count = count + 1
            print("did test: " + str(count) + " times.")
            if count >= 3:
               await cooldown())
            while count >= 3:
                if cool == 0:
                    count = 0
                else:
                    print("Command still on cooldown.")
        else:
            print("not a real command")


async def cooldown():
    global cool
    cool = 1
    print("Cooldown is activated.")
    time.sleep(3)
    cool = 0
    print("Cooldown is deactivated.")


asyncio.run(main())
2 Answers

EDITED TO ADD A WORKING PROGRAM

To make your program work you need to deal with several issues.

  1. You need a non-blocking means of obtaining input from the user.
  2. You need to use asyncio.sleep() instead of time.sleep()
  3. Instead of awaiting the cooldown function, you need to create it as a task so it can run in parallel to main().
  4. You need to add sleep calls to main so your cooldown task gets an opportunity to run.

To deal with #1 I replaced your input function with calls to kbhit and getch, both functions from msvcrt (Microsoft Visual C runtime). So this program will only run on windows. So user inputs are just one character. The letter "t" means the same as "test" in your program.

I removed both of your global variables, which were unnecessary.

If there is no input from the user, main() sleeps for 0.1 second.

If the user strikes the "t" key 3 times, main() creates a task, which runs as soon as main() reaches its next await statement.

To see if the task is finished, call its "done" method. You do not need a global variable to pass around this information.

Otherwise the logic is similar to yours and so are the print statements. The program works fine with Python3.8 and Windows10.

I found the documentation for asyncio to be extremely confusing, and it took me a long time to figure out the basics. What seems to be missing is an introduction that explains the basic concepts. Once you get past that it all works beautifully but the learning curve is steeper than it needs to be.

# python3.8

from msvcrt import kbhit, getch
import asyncio
import time

async def main():
    count = 0
    task = None
    print("Hit a key to make things happen")

    while True:
        if kbhit():
            c = str(getch(), encoding='utf-8')
            print("Key pressed", c, count)
        else:
            await asyncio.sleep(0.1)
            continue
        if c == "t":
            count += 1
            print("did test: " + str(count) + " times.")
            if count >= 3:
                if task is None or task.done():
                    task = asyncio.create_task(cooldown())
        if task is not None and not task.done():
            print("Cooling")

async def cooldown():
    print("Cooldown is activated.")
    await asyncio.sleep(3.0)
    print("Cooldown is deactivated.")

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

ORIGINAL ANSWER: Change from time.sleep to asyncio.sleep and await on it:

async def cooldown():
    global cool
    cool = 1
    print("Cooldown is activated.")
    await asyncio.sleep(3)
    cool = 0
    print("Cooldown is deactivated.")

time.sleep() blocks the current thread. asyncio.sleep is awaitable, so when you await on it the thread is not blocked. Other tasks can run.

I also see that you await cooldown() in your main coroutine. The main coroutine will not continue until cooldown is finished. Is that what you want? Perhaps it would be better to replace the await cooldown() with this:

asyncio.create_task(cooldown())

In that case your main() function will proceed. But you won't see the print statement at the end of cooldown() because there is no await expression in main(). To deal with that, put the line

await asyncio.sleep(0)

somewhere in your loop. That will allow the event loop to switch tasks and you will see the print statement.

Be aware that the input() statement is blocking, and when your main() function calls input, none of your coroutines can proceed until you type something.

Although I don't know too much about threads in Java or Python I think this may help you solve your issue.

https://realpython.com/intro-to-python-threading

Threads allow you to run multiple functions simultaneously for example a thread can be taking keyboard inputs while the other thread can be drawing objects to screens. At least that's what I understood.

Related