简体   繁体   English

asyncio:等待来自其他线程的事件

[英]asyncio: Wait for event from other thread

I'm designing an application in Python which should access a machine to perform some (lengthy) tasks.我正在用 Python 设计一个应用程序,它应该访问一台机器来执行一些(冗长的)任务。 The asyncio module seems to be a good choice for everything that is network-related, but now I need to access the serial port for one specific component. asyncio 模块似乎是所有与网络相关的东西的好选择,但现在我需要访问一个特定组件的串行端口。 I've implemented kind of an abstraction layer for the actual serial port stuff, but can't figure out how to sensibly integrate this with asyncio.我已经为实际的串行端口实现了一种抽象层,但无法弄清楚如何将它与 asyncio 合理地集成。

Following setup: I have a thread running a loop, which regularly talks to the machine and decodes the responses.以下设置:我有一个运行循环的线程,它定期与机器对话并解码响应。 Using a method enqueue_query()<\/code> , I can put a query string into a queue, which will then be sent to the machine by the other thread and cause a response.使用方法enqueue_query()<\/code> ,我可以将查询字符串放入队列中,然后由另一个线程将其发送到机器并引起响应。 By passing in a threading.Event<\/code> (or anything with a set()<\/code> method), the caller can perform a blocking wait for the response.通过传入threading.Event<\/code> (或任何带有set()<\/code>方法的东西),调用者可以对响应执行阻塞等待。 This can then look something like this:这看起来像这样:

f = threading.Event()
ch.enqueue_query('2 getnlimit', f)
f.wait()
print(ch.get_query_responses())

By passing in a threading.Event (or anything with a set() method), the caller can perform a blocking wait for the response. 通过传入threading.Event (或带有set()方法的任何东西),调用者可以执行阻塞等待响应。

Given the above behavior of your query function, all you need is a thread-safe version of asyncio.Event . 鉴于查询函数的上述行为,您只需要asyncio.Event的线程安全版本。 It's just 3 lines of code: 这只是3行代码:

import asyncio
class Event_ts(asyncio.Event):
    #TODO: clear() method
    def set(self):
        #FIXME: The _loop attribute is not documented as public api!
        self._loop.call_soon_threadsafe(super().set)

A test for functionality: 功能测试:

def threaded(event):
    import time
    while True:
        event.set()
        time.sleep(1)

async def main():
    import threading
    e = Event_ts()
    threading.Thread(target=threaded, args=(e,)).start()
    while True:
        await e.wait()
        e.clear()
        print('whatever')

asyncio.ensure_future(main())
asyncio.get_event_loop().run_forever()

The simplest way is to do exactly what you suggested - wrap the call to f.wait() in an executor: 最简单的方法是完全按照你的建议 - 在执行器中将调用包装到f.wait()

@asyncio.coroutine
def do_enqueue():
    f = threading.Event()
    ch.enqueue_query('2 getnlimit', f)
    yield from loop.run_in_executor(None, f.wait)
    print(ch.get_query_responses())

You do incur the overhead of starting up a thread pool (at least for the first call, the pool will stay in memory from that point forward), but any solution that provides an implementation of something like threading.Event() with thread-safe blocking and non-blocking APIs, without relying on any background threads internally, would be quite a bit more work. 你做招致启动一个线程池(至少在第一次调用,池将留在从此时内存)的开销,但提供类似的东西实现的任何解决方案threading.Event()与线程安全阻塞和非阻塞API,而不依赖于内部的任何后台线程,将是相当多的工作。

The class Event_ts in Huazuo Gao's answer works well in Python up to 3.9 but not in 3.10. Event_ts Gao 的答案中的 Event_ts 类在 Python 3.9 中运行良好,但在 3.10 中运行良好。 It is because in Python 3.10 the private attribute _loop is initially None .这是因为在 Python 3.10 中,私有属性_loop最初是None

The following code works in Python 3.10 as well as 3.9 and below.以下代码适用于 Python 3.10 以及 3.9 及更低版本。 (I added the clear() method as well.) (我也添加了clear()方法。)

import asyncio
class Event_ts(asyncio.Event):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self._loop is None:
            self._loop = asyncio.get_event_loop()

    def set(self):
        self._loop.call_soon_threadsafe(super().set)

    def clear(self):
        self._loop.call_soon_threadsafe(super().clear)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM