我们假设我有以下代码:
import asyncio
import threading
queue = asyncio.Queue()
def threaded():
import time
while True:
time.sleep(2)
queue.put_nowait(time.time())
print(queue.qsize())
@asyncio.coroutine
def async():
while True:
time = yield from queue.get()
print(time)
loop = asyncio.get_event_loop()
asyncio.Task(async())
threading.Thread(target=threaded).start()
loop.run_forever()
此代码的问题在于async
协同程序中的循环永远不会完成第一次迭代,而queue
大小正在增加。
为什么会以这种方式发生这种情况,我该怎么做才能解决这个问题?
我无法摆脱单独的线程,因为在我的实际代码中,我使用单独的线程与串行设备进行通信,而我找不到使用asyncio
的方法。
答案 0 :(得分:20)
asyncio.Queue
is not thread-safe,因此您无法直接在多个主题中使用它。相反,您可以使用janus
,它是提供线程感知asyncio
队列的第三方库:
import asyncio
import threading
import janus
def threaded(squeue):
import time
while True:
time.sleep(2)
squeue.put_nowait(time.time())
print(squeue.qsize())
@asyncio.coroutine
def async(aqueue):
while True:
time = yield from aqueue.get()
print(time)
loop = asyncio.get_event_loop()
queue = janus.Queue(loop=loop)
asyncio.Task(asyncio.ensure_future(queue.async_q))
threading.Thread(target=threaded, args=(queue.sync_q,)).start()
loop.run_forever()
还有aioprocessing
(完全披露:我写了它),它也提供了进程安全(以及作为副作用,线程安全)的队列,但是这样做太过分了。您并未尝试使用multiprocessing
。
答案 1 :(得分:4)
BaseEventLoop.call_soon_threadsafe
即将到来。有关详细信息,请参阅asyncio
doc。
只需像这样更改threaded()
:
def threaded():
import time
while True:
time.sleep(1)
loop.call_soon_threadsafe(queue.put_nowait, time.time())
loop.call_soon_threadsafe(lambda: print(queue.qsize()))
这是一个示例输出:
0
1443857763.3355968
0
1443857764.3368602
0
1443857765.338082
0
1443857766.3392274
0
1443857767.3403943
答案 2 :(得分:4)
如果您不想使用其他库,可以从该线程安排协程。用以下内容替换queue.put_nowait
可以正常工作。
asyncio.run_coroutine_threadsafe(queue.put(time.time()), loop)
变量loop
表示主线程中的事件循环。
编辑:
你的async
协程没有做任何事情的原因是这样的
事件循环从未给它机会这样做。队列对象是
不是线程安全的,如果你仔细查看cpython代码就可以找到它
这意味着put_nowait
通过唤醒队列的消费者
使用事件循环的call_soon
方法的未来。如果
我们可以使用call_soon_threadsafe
它应该有效。专业
然而,call_soon
和call_soon_threadsafe
之间存在差异
call_soon_threadsafe
通过调用loop._write_to_self()
唤醒事件循环。所以我们自己来称呼它:
import asyncio
import threading
queue = asyncio.Queue()
def threaded():
import time
while True:
time.sleep(2)
queue.put_nowait(time.time())
queue._loop._write_to_self()
print(queue.qsize())
@asyncio.coroutine
def async():
while True:
time = yield from queue.get()
print(time)
loop = asyncio.get_event_loop()
asyncio.Task(async())
threading.Thread(target=threaded).start()
loop.run_forever()
然后,一切都按预期工作。
至于线程安全方面
访问共享对象,asyncio.queue
在引擎盖下使用
collections.deque
,其中包含线程安全append
和popleft
。
也许检查队列不是空的并且popleft不是原子的,但是如果
您只在一个线程(事件循环之一)中使用队列
没关系。
其他提出的解决方案,来自华佐的loop.call_soon_threadsafe
高的答案和我的asyncio.run_coroutine_threadsafe
正在做
这个,唤醒事件循环。
答案 3 :(得分:0)
仅将threading.Lock与asyncio.Queue一起使用怎么办?
class ThreadSafeAsyncFuture(asyncio.Future):
""" asyncio.Future is not thread-safe
https://stackoverflow.com/questions/33000200/asyncio-wait-for-event-from-other-thread
"""
def set_result(self, result):
func = super().set_result
call = lambda: func(result)
self._loop.call_soon_threadsafe(call) # Warning: self._loop is undocumented
class ThreadSafeAsyncQueue(queue.Queue):
""" asyncio.Queue is not thread-safe, threading.Queue is not awaitable
works only with one putter to unlimited-size queue and with several getters
TODO: add maxsize limits
TODO: make put corouitine
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.lock = threading.Lock()
self.loop = asyncio.get_event_loop()
self.waiters = []
def put(self, item):
with self.lock:
if self.waiters:
self.waiters.pop(0).set_result(item)
else:
super().put(item)
async def get(self):
with self.lock:
if not self.empty():
return super().get()
else:
fut = ThreadSafeAsyncFuture()
self.waiters.append(fut)
result = await fut
return result