【问题标题】:How to do a loop without holding up the system如何在不阻塞系统的情况下进行循环
【发布时间】:2020-05-14 01:13:49
【问题描述】:

我正在尝试执行一个在 python 中激活冷却计时器的命令,但我遇到了问题。它锁定了整个程序,在冷却结束之前不再允许任何其他输入。我希望能够在循环等待 X 秒通过时输入更多命令。

例子:

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())

【问题讨论】:

  • time.sleep() 阻塞,你应该使用 asyncio.sleep
  • 尝试使用 async.io sleep 和 time.sleep 仍然会阻塞程序。

标签: python python-3.x asynchronous python-asyncio


【解决方案1】:

编辑添加一个工作程序

要使您的程序正常运行,您需要处理几个问题。

  1. 您需要一种非阻塞方式来获取用户的输入。
  2. 您需要使用 asyncio.sleep() 而不是 time.sleep()
  3. 您需要将其创建为一个任务,以便它可以与 main() 并行运行,而不是等待冷却功能。
  4. 您需要向 main 添加 sleep 调用,以便您的冷却任务有机会运行。

为了处理 #1,我将您的输入函数替换为对 kbhit 和 getch 的调用,这两个函数都来自 msvcrt(Microsoft Visual C 运行时)。所以这个程序只能在windows上运行。所以用户输入只是一个字符。字母“t”与程序中的“test”含义相同。

我删除了你的两个全局变量,这是不必要的。

如果没有用户输入,main() 会休眠 0.1 秒。

如果用户按“t”键 3 次,main() 会创建一个任务,该任务会在 main() 到达下一个 await 语句时立即运行。

要查看任务是否完成,请调用其“完成”方法。您不需要全局变量来传递此信息。

否则逻辑与您的相似,打印语句也是如此。该程序适用于 Python3.8 和 Windows10。

我发现 asyncio 的文档非常混乱,我花了很长时间才弄清楚基础知识。似乎缺少的是解释基本概念的介绍。一旦你过去了,一切都很好,但学习曲线比它需要的要陡峭。

# 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())

原始答案: 从 time.sleep 更改为 asyncio.sleep 并等待它:

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

time.sleep() 阻塞当前线程。 asyncio.sleep 是可等待的,因此当您在其上等待时,线程不会被阻塞。可以运行其他任务。

我还看到您在主协程中等待 cooldown()。主协程在冷却完成之前不会继续。那是你要的吗?也许用这个替换 await cooldown() 会更好:

asyncio.create_task(cooldown())

在这种情况下,您的 main() 函数将继续执行。但是你不会在 cooldown() 结束时看到 print 语句,因为 main() 中没有 await 表达式。为了解决这个问题,把线放在

await asyncio.sleep(0)

在你的循环中的某个地方。这将允许事件循环切换任务,您将看到打印语句。

注意 input() 语句是阻塞的,当你的 main() 函数调用 input 时,你的协程都不能继续,直到你输入一些东西。

【讨论】:

  • 请注意,await asyncio.sleep(0) 在某种程度上是一种反模式。正确的做法是等待冷却时间设置的event,而不是使用全局变量的值进行通信。
  • 我尝试过使用 asyncio.sleep(3),但它仍然会在等待冷却 () 的同时锁定代码。目标是在 3 次后写入字符串“test”之前等待 3 秒,但不会锁定用户输入
  • 对不起,我直到现在才看到这条评论。您不理解我关于将 await asyncio.sleep(0) 放入循环中的评论。忽略说这是反模式的评论,因为对于 asyncio 程序,它不是。为了并行运行两个任务,两个任务都必须包含 await 语句。否则,没有等待的任务将在获得控制后立即运行到最后。你不能通过添加一行代码来修复整个程序:你必须将“输入”语句替换为非阻塞函数,并且将 await(s) 放在两个任务中。
【解决方案2】:

虽然我对 Java 或 Python 中的线程不太了解,但我认为这可能会帮助您解决问题。

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

线程允许您同时运行多个功能,例如,一个线程可以接受键盘输入,而另一个线程可以将对象绘制到屏幕上。至少我是这么理解的。

【讨论】:

  • 问题明显是关于 asyncio,它可以被认为是线程的替代。同一个站点也有一个intro to asyncio
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-07-01
  • 2019-09-12
  • 2014-12-09
  • 2015-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多