【问题标题】:Asynchronous image search on screen with Trio使用 Trio 在屏幕上进行异步图像搜索
【发布时间】:2020-09-12 12:03:25
【问题描述】:

我正在尝试调整this 模块以在给定时间在同一屏幕截图中搜索大量图像时支持异步执行。我对异步编码有点陌生,经过大量研究后,我选择了 Trio 来做它(因为它很棒而且很容易)。

重点是:

  • 函数接收图片路径列表
  • 在每次迭代时,它都会截取屏幕截图并尝试在数组中查找图像(如果我们不为数组中的每次尝试都截取新的屏幕截图,这会更好地提高性能)
  • 如果找到,则返回图像的路径和坐标
  • 重新做一遍,因为现在屏幕上可能会出现一些图像

我将在另一个支持与 Trio 异步的项目中使用它,这就是我尝试转换它的原因。

这是我的尝试:


def image_search(image, precision=0.8, pil=None):
    if pil is None:
        pil = pyautogui.screenshot()
    if is_retina:
        pil.thumbnail((round(pil.size[0] * 0.5), round(pil.size[1] * 0.5)))
    return most_probable_location(pil, image, precision)

async def multiple_image_search_loop(images, interval=0.1, timeout=None, precision=0.8):
    async def do_search():
        while True:
            pil = pyautogui.screenshot()
            for image in images:
                if pos := image_search(image, precision, pil):
                    return {
                        "position": pos,
                        "image": image
                    }
            await trio.sleep(interval)

    if timeout:
        with trio.fail_after(timeout):
            return await do_search()
    else:
        return await do_search()

虽然代码看起来正确,但我觉得我错过了异步代码的要点。这一切都可以以同步的方式完成,我觉得我没有做任何改变。

如果在性能上没有差异,那还不错,因为关键是要使此函数在异步上下文中有用,而不会在搜索图像的整个过程中阻塞,但是如果我可以优化一些东西,那肯定会更好。

也许如果不是awaiting 在搜索所有图像后我调整image_search() 并调用trio.sleep() 并在主要功能上打开一个托儿所会更好? (对阵列上的每个图像使用其中的trio.start_soon() 方法)。这会减少对我将要使用的其他项目的阻碍,但查找图像需要更多时间,对吗?

【问题讨论】:

    标签: python async-await python-trio


    【解决方案1】:

    Trio 不会像这样直接并行化 CPU-bound 代码。作为“异步框架”意味着它只使用单个 CPU 线程,同时并行化 I/O 和网络操作。如果您插入一些对await trio.sleep(0) 的调用,那么这将使 Trio 将图像搜索与其他任务交错,但不会使图像搜索更快。

    不过,您可能想要做的是使用单独的线程。我猜你的代码可能大部分时间都花在了opencv上,而opencv可能会放弃GIL?所以使用线程可能会让你一次在多个 CPU 上运行你的代码,同时也让其他异步任务同时运行。要像这样管理线程,Trio 允许您执行 await trio.to_thread.run_sync(some_sync_function, *args),它在线程中运行 some_sync_function(*args)。如果您在 Nursery 中同时运行多个这样的调用,那么您将使用多个线程。

    线程有一个主要问题需要注意:一旦trio.to_thread.run_sync 调用开始,它就不能被取消,所以超时等直到调用结束后才会生效。要解决此问题,您可能需要确保各个呼叫不会阻塞太久。

    另外,关于风格的附注:为 Trio 制作的函数通常不采用 timeout= 这样的参数,因为如果用户想要添加超时,他们可以在你的函数周围编写 with 块自己就像传递一个论点一样容易。所以这样你就不必到处乱用超时参数了。

    【讨论】:

    • 差点和trio.to_thread.run_sync(some_sync_function, *args)一起哭了。漂亮的图书馆,谢谢!
    猜你喜欢
    • 2012-12-02
    • 1970-01-01
    • 2011-06-26
    • 1970-01-01
    • 2015-06-18
    • 2014-09-29
    • 2017-02-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多