我需要创建一个可以同时从Web套接字和管道接收并在另一个通道上发送消息的软件(它从套接字接收消息,创建一个新线程并发送到管道。以与从接收消息相同的方式管道,创建一个新线程并发送到套接字。
我在使用多线程时遇到问题,在程序启动时,我必须启动方法socket_receiver
和pipe_receiver
,但是我只能启动pipe_receiver
。我尝试删除所有代码,仅保留socket_receiver
和pipe_receiver
,但它们仅输入while True
的{{1}}。
pipe_receiver
该程序由子进程调用,父进程通过连接到stdout和stdin的管道与之通信。
更新:我收到@Martijn Pieters代码的异常
import asyncio
import sys
import json
from concurrent.futures.thread import ThreadPoolExecutor
import websockets
# make the Pool of workers
executor = ThreadPoolExecutor(max_workers=10)
# Make connection to socket and pipe
header = {"Authorization": r"Basic XXXX="}
connection = websockets.connect('wss://XXXXXXXX', extra_headers=header)
async def socket_receiver():
"""Listening from web socket"""
async with connection as web_socket:
while True:
message = await web_socket.recv()
# send the message to the pipe in a new thread
executor.submit(send_to_pipe(message))
async def pipe_receiver():
"""Listening from pipe"""
while True:
message = sys.stdin.readline()
if not message:
break
executor.submit(send_to_socket(message))
# jsonValue = json.dump(str(line), file);
sys.stdout.flush()
def send_to_pipe(message):
# Check if message is CAM or DENM
json_message = json.loads(message)
type = int(json_message["header"]["messageID"])
# 1 is DENM message, 2 is CAM message
if type == 1 or type == 2:
# send the message to the pipe
sys.stdout.print(json_message);
async def send_to_socket(message):
async with connection as web_socket:
json_message = json.dumps(message)
await web_socket.send(json_message)
asyncio.get_event_loop().run_until_complete(
asyncio.gather(socket_receiver(),pipe_receiver()))
答案 0 :(得分:4)
您没有正确使用return_when=asyncio.FIRST_COMPLETED
,并且您真的不想在这里使用它。相反,您需要设置使用者和生产者以使用队列来处理套接字和管道,以在它们之间发送消息。
对于每种连接类型,创建一个协程以创建连接,然后将该单个连接传递给该连接的使用者和生产者任务(用asyncio.create_task()
创建) 。使用asyncio.wait()
与sys.stdin
一起运行两个任务,因此您可以取消两个任务之一完成“较早”(例如失败)时仍在运行的任务。
使用queue将消息从一个使用者的消费者传递到另一个连接的使用者。
sys.stdout
和async with
是阻塞流,不要只是对其进行读写!有关尝试建立无阻塞STDIO流的要点,请参见https://gist.github.com/nathan-hoad/8966377,以及要求无阻塞流功能的this asyncio issue。
不要使用全局套接字连接,当然不要使用两个单独的send_to_socket()
语句。您的async with connection as web_socket:
方法实际上会关闭套接字,因为在发送第一条消息时socket_receiver
上下文管理器会退出,然后会导致executor.submit(send_to_pipe(message))
代码出现问题假设插座无限期保持打开状态。
不要在这里使用线程!您的连接完全由asyncio进行管理,因此,线程连接会因此而脚。
asyncio.Executor()
instances仅可用于常规可调用对象,而不可用于协程。 Executor.submit()
指出它需要一个可调用对象,用executor.submit(send_to_socket(message))
或socket_receiver()
传递一个协程将导致异常,因为协程不是可调用的。您可能没有看到异常消息,因为该异常在另一个线程中引发。
这是您的RuntimeWarning: coroutine 'send_to_socket' was never awaited
executor.submit(send_to_socket(message))
协程失败的原因;它肯定是启动,但是尝试发送消息失败。当我在本地模拟的websocket服务器上运行您的代码时,会显示警告:
try: callable(), except Exception: traceback.print_exc(file=sys.stderr))
当不等待协程时,该协程中的代码将永远不会执行。将协程包装成一个输出stderr(Traceback (most recent call last):
File "soq52219672.py", line 15, in log_exception
callable()
TypeError: 'coroutine' object is not callable
)异常的包,您将得到:
asyncio
执行器仅应用于集成不能使用协程转换为的代码。执行者管理该代码使其与asyncio
任务并行运行而不会受到干扰。如果该代码想与stdio()
任务交互,则应格外小心,请始终使用asyncio.run_coroutine_threadsafe()
或asyncio.call_soon_threadsafe()
来越界调用。参见Concurrency and multithreading section。
这是一个示例,该示例说明了如何重写代码以使用使用者/生产者模式,其中基于Nathan Hoad gist on the subject的import asyncio
import json
import os
import sys
import websockets
async def socket_consumer(socket, outgoing):
# take messages from the web socket and push them into the queue
async for message in socket:
await outgoing.put(message)
async def socket_producer(socket, incoming):
# take messages from the queue and send them to the socket
while True:
message = await incoming.get()
jsonmessage = json.dumps(message)
await socket.send(jsonmessage)
async def connect_socket(incoming, outgoing):
header = {"Authorization": r"Basic XXXX="}
uri = 'wss://XXXXXXXX'
async with websockets.connect(uri, extra_headers=header) as websocket:
# create tasks for the consumer and producer. The asyncio loop will
# manage these independently
consumer_task = asyncio.create_task(socket_consumer(websocket, outgoing))
producer_task = asyncio.create_task(socket_producer(websocket, incoming))
# start both tasks, but have the loop return to us when one of them
# has ended. We can then cancel the remainder
done, pending = await asyncio.wait(
[consumer_task, producer_task],
return_when=asyncio.FIRST_COMPLETED
)
for task in pending:
task.cancel()
# force a result check; if there was an exception it'll be re-raised
for task in done:
task.result()
# pipe support
async def stdio(loop=None):
if loop is None:
loop = asyncio.get_running_loop()
if sys.platform == 'win32':
# no support for asyncio stdio yet on Windows, see https://bugs.python.org/issue26832
# use an executor to read from stdio and write to stdout
class Win32StdinReader:
def __init__(self):
self.stdin = sys.stdin.buffer
async def readline():
# a single call to sys.stdin.readline() is thread-safe
return await loop.run_in_executor(None, self.stdin.readline)
class Win32StdoutWriter:
def __init__(self):
self.buffer = []
self.stdout = sys.stdout.buffer
def write(self, data):
self.buffer.append(data)
async def drain(self):
data, self.buffer = self.buffer, []
# a single call to sys.stdout.writelines() is thread-safe
return await loop.run_in_executor(None, sys.stdout.writelines, data)
return Win32StdinReader(), Win32StdoutWriter()
reader = asyncio.StreamReader()
await loop.connect_read_pipe(
lambda: asyncio.StreamReaderProtocol(reader),
sys.stdin
)
writer_transport, writer_protocol = await loop.connect_write_pipe(
asyncio.streams.FlowControlMixin,
os.fdopen(sys.stdout.fileno(), 'wb')
)
writer = asyncio.streams.StreamWriter(writer_transport, writer_protocol, None, loop)
return reader, writer
async def pipe_consumer(pipereader, outgoing):
# take messages from the pipe and push them into the queue
while True:
message = await pipereader.readline()
if not message:
break
await outgoing.put(message.decode('utf8'))
async def pipe_producer(pipewriter, incoming):
# take messages from the queue and send them to the pipe
while True:
jsonmessage = await incoming.get()
message = json.loads(jsonmessage)
type = int(message.get('header', {}).get('messageID', -1))
# 1 is DENM message, 2 is CAM message
if type in {1, 2}:
pipewriter.write(jsonmessage.encode('utf8') + b'\n')
await pipewriter.drain()
async def connect_pipe(incoming, outgoing):
reader, writer = await stdio()
# create tasks for the consumer and producer. The asyncio loop will
# manage these independently
consumer_task = asyncio.create_task(pipe_consumer(reader, outgoing))
producer_task = asyncio.create_task(pipe_producer(writer, incoming))
# start both tasks, but have the loop return to us when one of them
# has ended. We can then cancel the remainder
done, pending = await asyncio.wait(
[consumer_task, producer_task],
return_when=asyncio.FIRST_COMPLETED
)
for task in pending:
task.cancel()
# force a result check; if there was an exception it'll be re-raised
for task in done:
task.result()
async def main():
pipe_to_socket = asyncio.Queue()
socket_to_pipe = asyncio.Queue()
socket_coro = connect_socket(pipe_to_socket, socket_to_pipe)
pipe_coro = connect_pipe(socket_to_pipe, pipe_to_socket)
await asyncio.gather(socket_coro, pipe_coro)
if __name__ == '__main__':
asyncio.run(main())
以及在Windows support for treating stdio as pipes is limited上的备用版本:
{{1}}
然后从两个任务开始,一个任务管理套接字,另一个任务管理STDIO管道。两家公司都分别为其消费者和生产者启动了2个任务。有两个队列将消息从一个的消费者发送到另一个的生产者。