我想在执行同步功能期间建立SSH SOCKs隧道(使用asyncssh
)。功能完成后,我想拆除隧道并退出。
很显然,必须等待一些异步功能以使隧道正常工作,因此重要的是conn.wait_closed()
和同步功能是同时执行的。所以我很确定我确实需要第二个线程。
我首先使用ThreadPoolExecutor
和run_in_executor
尝试了一些更聪明的方法,但最终遇到了下面的糟糕透顶的变体。
#! /usr/bin/env python3
import traceback
from threading import Thread
from concurrent.futures import ThreadPoolExecutor
import asyncio, asyncssh, sys
_server="127.0.0.1"
_port=22
_proxy_port=8080
async def run_client():
conn = await asyncio.wait_for(
asyncssh.connect(
_server,
port=_port,
options=asyncssh.SSHClientConnectionOptions(client_host_keysign=True),
),
10,
)
listener = await conn.forward_socks('127.0.0.1', _proxy_port)
return conn
async def do_stuff(func):
try:
conn = await run_client()
print("SSH tunnel active")
def start_loop(loop):
asyncio.set_event_loop(loop)
try:
loop.run_forever()
except Exception as e:
print(f"worker loop: {e}")
async def thread_func():
ret=await func()
print("Func done - tearing done worker thread and SSH connection")
conn.close()
# asyncio.get_event_loop().stop()
return ret
func_loop = asyncio.new_event_loop()
func_thread = Thread(target=start_loop, args=(func_loop,))
func_thread.start()
print("thread started")
fut = asyncio.run_coroutine_threadsafe(thread_func(), func_loop)
print(f"fut scheduled: {fut}")
done = await asyncio.gather(asyncio.wrap_future(fut), conn.wait_closed())
print("wait done")
for ret in done:
print(f"ret={ret}")
# Canceling pending tasks and stopping the loop
# asyncio.gather(*asyncio.Task.all_tasks()).cancel()
print("stopping func_loop")
func_loop.call_soon_threadsafe(func_loop.stop())
print("joining func_thread")
func_thread.join()
print("joined func_thread")
except (OSError, asyncssh.Error) as exc:
sys.exit('SSH connection failed: ' + str(exc))
except (Exception) as exc:
sys.exit('Unhandled exception: ' + str(exc))
traceback.print_exc()
async def just_wait():
print("starting just_wait")
input()
print("ending just_wait")
return 42
asyncio.get_event_loop().run_until_complete(do_stuff(just_wait))
实际上,它“正确”地“正常”运行,直到join
设置工作线程时出现异常为止。我猜想是因为我做的事情不是线程安全的。
Exception in callback None()
handle: <Handle>
Traceback (most recent call last):
File "/usr/lib/python3.7/asyncio/events.py", line 88, in _run
self._context.run(self._callback, *self._args)
TypeError: 'NoneType' object is not callable
要测试代码,您必须具有运行本地SSH服务器并为您的用户设置密钥文件。您可能需要更改_port
变量。
我正在寻找异常的原因和/或程序版本,该版本在线程中需要较少的人工干预,并且可能仅使用单个事件循环。当我想await
两件事时(例如在asyncio.gather
通话中),我不知道如何实现后者。
答案 0 :(得分:2)
直接导致错误的原因是此行:
func_loop.call_soon_threadsafe(func_loop.stop())
目的是在运行func_loop.stop()
事件循环的线程中调用func_loop
。但是按照编写,它会在当前线程中调用func_loop.stop()
,并将其返回值(None
)传递给call_soon_threadsafe
作为要调用的函数。这导致call_soon_threadsafe
抱怨无人可呼。要解决当前的问题,您应该删除多余的括号并以func_loop.call_soon_threadsafe(func_loop.stop)
的形式调用该方法。
但是,代码在编写时绝对过于复杂:
just_wait
不应为async def
,因为它不会等待任何东西,因此显然不是异步的。sys.exit
采用整数退出状态,而不是字符串。另外,尝试在对sys.exit
的调用后 打印回溯记录没有多大意义。要从asyncio运行非异步功能,只需将run_in_executor
与该功能一起使用,并将其原样传递给非异步功能。您不需要额外的线程,也不需要额外的事件循环,run_in_executor
将负责处理该线程并将其与当前事件循环连接,从而有效地使sync函数处于等待状态。例如(未试用):
async def do_stuff(func):
conn = await run_client()
print("SSH tunnel active")
loop = asyncio.get_event_loop()
ret = await loop.run_in_executor(None, func)
print(f"ret={ret}")
conn.close()
await conn.wait_closed()
print("wait done")
def just_wait():
# just_wait is a regular function; it can call blocking code,
# but it cannot await
print("starting just_wait")
input()
print("ending just_wait")
return 42
asyncio.get_event_loop().run_until_complete(do_stuff(just_wait))
如果您需要等待just_wait
中的内容,可以将其设置为async
,并使用run_in_executor
作为其中的实际阻止代码:
async def do_stuff():
conn = await run_client()
print("SSH tunnel active")
loop = asyncio.get_event_loop()
ret = await just_wait()
print(f"ret={ret}")
conn.close()
await conn.wait_closed()
print("wait done")
async def just_wait():
# just_wait is an async function, it can await, but
# must invoke blocking code through run_in_executor
print("starting just_wait")
loop = asyncio.get_event_loop()
await loop.run_in_executor(None, input)
print("ending just_wait")
return 42
asyncio.run(do_stuff())