使用Trio在屏幕上进行异步图像搜索

2024-04-27 21:36:57 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试调整this模块,以便在给定时间在同一屏幕截图中搜索大量图像时支持异步执行。我对异步编码有点陌生,经过大量的研究,我选择了Trio来做这件事(因为它既令人惊叹又简单)

重点是:

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

我将在另一个支持async with 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()

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

如果在性能上没有差异,那也没什么不好的,因为关键是要使这个函数在异步上下文中有用,而不会在搜索图像的整个过程中阻塞,但是如果我可以优化,它肯定会更好

也许如果在搜索所有图像后,我通过调用trio.sleep()来调整image_search()并在主函数上打开托儿所,而不是awaiting会更好?(对于阵列上的每个图像,使用其中的trio.start_soon()方法)。这会减少我将要使用它的另一个项目上的阻塞,但它会花费更多的时间来查找图像,对吗


Tags: 函数图像imagenonesearchasynctrioreturn
1条回答
网友
1楼 · 发布于 2024-04-27 21:36:57

Trio不会像这样直接并行CPU绑定的代码。作为一个“异步框架”,意味着它只使用一个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)。如果在托儿所中同时运行多个这样的调用,那么将使用多个线程

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

另外,还有一个关于样式的旁注:为Trio编写的函数通常不接受这样的timeout=参数,因为如果用户想要添加超时,他们可以像传递参数一样轻松地在函数周围编写with块。因此,这样您就不必到处都用超时参数来扰乱API

相关问题 更多 >