【问题标题】:Is there any way to guarantee asyncio.Task will be started immediately?有什么方法可以保证 asyncio.Task 会立即启动?
【发布时间】:2015-11-30 10:03:23
【问题描述】:
import asyncio


l = asyncio.Lock()

async def test():
    print('locked' if l.locked() else 'unlocked')

    await l.acquire()
    # await asyncio.ensure_future(l.acquire())

    await asyncio.sleep(1)
    l.release()

async def main():
    await asyncio.gather(test(), test())

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

我们启动两个test()协程,第一个立即通过l.acquire()锁定Lock,第二个打印locked状态。输出:

unlocked
locked

如果您评论await l.acquire() 行并取消下一行的评论,一切都会改变。输出将是:

unlocked
unlocked

这是因为包裹在 Task 中的 l.acquire() 在第二个 test() 启动之后启动。

有没有办法让l.acquire() 任务在第二个test() 之前尽快启动(并获得与原始代码相同的输出)?

【问题讨论】:

  • 我猜这是不可能的,因为事件循环以它们到达的相同顺序将任务从_ready双端队列中弹出。在你的例子中,这个顺序是main()test()test()l.acquire()。但是你为什么要把l.acquire()安排成一个任务呢?
  • @Vincent 这是合成的例子,在现实生活中我使用asyncio.gather(l1.acquire(), l2...) 之类的东西来启动并行资源阻塞,但它在任务中包装了协程。
  • 好的,所以它与您的other question 有关。也许asyncio.gather 不是适合这项工作的工具...
  • @Vincent 谢谢,但这也是我的问题 :) 我需要解决这个问题才能找到第二个问题的答案。

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


【解决方案1】:

看起来我找到了解决方案。我们需要全局锁,它会在第一个任务启动时暂停第二个任务锁定检查:

import asyncio


l = asyncio.Lock()
check_lock = asyncio.Lock()

async def test():
    async with check_lock:
        print('locked' if l.locked() else 'unlocked')
        await asyncio.ensure_future(l.acquire())
        print('now', 'locked' if l.locked() else 'unlocked')

    await asyncio.sleep(1)
    l.release()

async def main():
    await asyncio.gather(test(), test())

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

输出:

unlocked
now locked
locked
# 1 second delay here
now locked

但是这里我们遇到了另一个问题:当第一个任务完全完成时,第二个任务会被挂起,这就是为什么我们在第二个锁定之前有 1 秒的延迟。在原始示例中很难看到(因为test() 等待单个锁定),这就是我添加第二次打印的原因。但是对于multiple locking of different resources 来说,立即启动协程可能很重要:我们应该只锁定创建任务,而不是等待它。创建任务本身不会立即启动该任务(和l.acquire()),我们应该将控制权返回给事件循环。可以通过await asyncio.sleep(0) 完成。这是原始代码的最终解决方案:

import asyncio


l = asyncio.Lock()
check_lock = asyncio.Lock()

async def test():
    async with check_lock:
        print('locked' if l.locked() else 'unlocked')
        task = asyncio.ensure_future(l.acquire())  # only create task
        await asyncio.sleep(0)  # return control to event loop, it allows lock to be locked before task completed
        print('now', 'locked' if l.locked() else 'unlocked')  # would be printed immediately

    await task  # and now we can await for task done
    await asyncio.sleep(1)
    l.release()

async def main():
    await asyncio.gather(test(), test())

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

输出:

unlocked
now locked
locked
# no delay here
now locked

【讨论】:

    猜你喜欢
    • 2019-12-22
    • 2016-10-11
    • 1970-01-01
    • 1970-01-01
    • 2013-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多