Python 3.5 asyncio在不同线程

时间:2016-03-30 19:30:36

标签: multithreading python-3.x coroutine python-asyncio event-loop

我希望有人可以帮助我。

我有一个能够拥有返回协程对象的属性的对象。这很好用,但是我需要在单独的线程中从同步代码获取coroutine对象的结果,而事件循环当前正在运行。我想出的代码是:

def get_sync(self, key: str, default: typing.Any=None) -> typing.Any:
    """
    Get an attribute synchronously and safely.

    Note:
        This does nothing special if an attribute is synchronous. It only
        really has a use for asynchronous attributes. It processes
        asynchronous attributes synchronously, blocking everything until
        the attribute is processed. This helps when running SQL code that
        cannot run asynchronously in coroutines.

    Args:
        key (str): The Config object's attribute name, as a string.
        default (Any): The value to use if the Config object does not have
            the given attribute. Defaults to None.

    Returns:
        Any: The vale of the Config object's attribute, or the default
        value if the Config object does not have the given attribute.
    """
    ret = self.get(key, default)

    if asyncio.iscoroutine(ret):
        if loop.is_running():
            loop2 = asyncio.new_event_loop()
            try:
                ret = loop2.run_until_complete(ret)

            finally:
                loop2.close()
        else:
            ret = loop.run_until_complete(ret)

    return ret

我正在寻找的是一种在多线程环境中同步获取协程对象结果的安全方法。对于我设置为提供它们的属性,self.get()可以返回一个协程对象。我发现的问题是:如果事件循环正在运行。在堆栈溢出和其他一些站点上搜索了几个小时之后,我的(破碎的)解决方案就在上面。如果循环正在运行,我创建一个新的事件循环并在新的事件循环中运行我的协同程序。除了代码永远挂在ret = loop2.run_until_complete(ret)行上之外,这是有效的。

目前,我的结果有以下几种情况:

  1. self.get()的结果不是协程
    • 返回结果。的 [好]
  2. self.get()的结果是一个协程&事件循环未运行(基本上与事件循环在同一个线程中)
    • 返回结果。的 [好]
  3. self.get()的结果是一个协程&事件循环正在运行(基本上在与事件循环不同的线程中)
    • 永远等待结果。的 [差]
  4. 有谁知道如何解决不良结果,以便获得我需要的价值?感谢。

    我希望我在这里有所了解。

    我确实有充分的理由使用线程;特别是我使用的SQLAlchemy不是异步的,我将SQLAlchemy代码发送到ThreadPoolExecutor来安全地处理它。但是,我需要能够从这些线程中查询这些异步属性,以便SQLAlchemy代码安全地获取某些配置值。不,我不会为了完成我需要的东西而从SQLAlchemy转移到另一个系统,所以请不要提供它的替代品。该项目太过遥远,无法切换一些如此重要的东西。

    我尝试使用asyncio.run_coroutine_threadsafe()loop.call_soon_threadsafe(),但都失败了。到目前为止,这已经让它变得最有效,我觉得我只是错过了一些明显的东西。

    当我有机会时,我会写一些代码来提供问题的例子。

    好的,我实现了一个示例案例,它的工作方式与我期望的一样。所以我的问题很可能是代码中的其他地方。如果我需要的话,保持开放并将改变问题以适应我的真正问题。

    有没有人有任何可能的想法,为什么来自concurrent.futures.Future的{​​{1}}会永远挂起而不是返回结果?

    遗憾的是,不会重复我的错误的示例代码如下:

    asyncio.run_coroutine_threadsafe()

    这是我的代码正在执行的精简版本,并且该示例有效,即使我的实际应用程序没有。我被困在哪里寻找答案。欢迎建议在哪里尝试追踪我的“永远卡住”问题,即使我上面的代码实际上没有复制问题。

2 个答案:

答案 0 :(得分:1)

您不太可能同时运行多个事件循环,因此这部分看起来非常错误:

    if loop.is_running():
        loop2 = asyncio.new_event_loop()
        try:
            ret = loop2.run_until_complete(ret)

        finally:
            loop2.close()
    else:
        ret = loop.run_until_complete(ret)

即使测试循环是否正在运行似乎也不是正确的方法。明确({only)运行循环到get_sync并使用run_coroutine_threadsafe安排协程可能更好:

def get_sync(self, key, loop):
    ret = self.get(key, default)
    if not asyncio.iscoroutine(ret):
        return ret
    future = asyncio.run_coroutine_threadsafe(ret, loop)
    return future.result()

编辑:挂起问题可能与在错误循环中调度的任务有关(例如,在调用协程时忘记了可选的loop参数)。使用PR 303(现已合并)时,此类问题应该更容易调试:当循环和未来不匹配时,会引发RuntimeError。因此,您可能希望使用最新版本的asyncio运行测试。

答案 1 :(得分:0)

好的,我通过采用不同的方法让我的代码正常工作。问题与使用具有文件IO的东西有关,我正在使用文件IO组件上的loop.run_in_executor()将其转换为协程。然后,我试图在从另一个线程调用的同步函数中使用它,在该函数上使用另一个loop.run_in_executor()进行处理。这是我的代码中一个非常重要的例程(在执行我的短代码时可能会被称为百万次或更多),我做出了一个决定,我的逻辑变得太复杂了。所以......我简单了。现在,如果我想异步使用文件IO组件,我明确使用我的“get_async()”方法,否则,我通过普通属性访问使用我的属性。

通过消除逻辑的复杂性,它使代码更清晰,更容易理解,更重要的是,它实际上有效。虽然我并不是100%确定我知道问题的根本原因(我相信它与处理属性的线程有关,然后又会启动另一个尝试在处理属性之前读取属性的线程,导致类似竞争条件和停止我的代码,但我永远无法复制我的应用程序之外的错误,不幸地完全证明了这一点),我能够通过它并继续我的开发工作。