【发布时间】:2018-12-19 17:47:44
【问题描述】:
我尝试从 Qt 应用程序运行 python3 异步外部命令。在我使用多处理线程来执行此操作而不冻结 Qt 应用程序之前。但是现在,我想用QThread 来做这件事,以便能够腌制并给出QtWindows 作为其他一些函数的参数(这里没有介绍)。我做到了,并在我的Windows 操作系统上成功测试了它,但我在Linux 操作系统上尝试了该应用程序,我收到以下错误:RuntimeError: Cannot add child handler, the child watcher does not have a loop attached
从那时起,我尝试隔离问题,并获得了下面复制问题的最小(尽可能)示例。
当然,正如我之前提到的,如果我将QThreadPool 替换为multiprocessing.thread 的列表,则此示例运行良好。我还意识到了令我惊讶的事情:如果我在示例的最后部分取消注释 rc = subp([sys.executable,"./HelloWorld.py"]) 行,它也可以工作。我无法解释为什么。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
## IMPORTS ##
from functools import partial
from PyQt5 import QtCore
from PyQt5.QtCore import QThreadPool, QRunnable, QCoreApplication
import sys
import asyncio.subprocess
# Global variables
Qpool = QtCore.QThreadPool()
def subp(cmd_list):
""" """
if sys.platform.startswith('linux'):
new_loop = asyncio.new_event_loop()
asyncio.set_event_loop(new_loop)
elif sys.platform.startswith('win'):
new_loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows
asyncio.set_event_loop(new_loop)
else :
print('[ERROR] OS not available for encodage... EXIT')
sys.exit(2)
rc, stdout, stderr= new_loop.run_until_complete(get_subp(cmd_list) )
new_loop.close()
if rc!=0 :
print('Exit not zero ({}): {}'.format(rc, sys.exc_info()[0]) )#, exc_info=True)
return rc, stdout, stderr
async def get_subp(cmd_list):
""" """
print('subp: '+' '.join(cmd_list) )
# Create the subprocess, redirect the standard output into a pipe
create = asyncio.create_subprocess_exec(*cmd_list, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) #
proc = await create
# read child's stdout/stderr concurrently (capture and display)
try:
stdout, stderr = await asyncio.gather(
read_stream_and_display(proc.stdout),
read_stream_and_display(proc.stderr))
except Exception:
proc.kill()
raise
finally:
rc = await proc.wait()
print(" [Exit {}] ".format(rc)+' '.join(cmd_list))
return rc, stdout, stderr
async def read_stream_and_display(stream):
""" """
async for line in stream:
print(line, flush=True)
class Qrun_from_job(QtCore.QRunnable):
def __init__(self, job, arg):
super(Qrun_from_job, self).__init__()
self.job=job
self.arg=arg
def run(self):
code = partial(self.job)
code()
def ThdSomething(job,arg):
testRunnable = Qrun_from_job(job,arg)
Qpool.start(testRunnable)
def testThatThing():
rc = subp([sys.executable,"./HelloWorld.py"])
if __name__=='__main__':
app = QCoreApplication([])
# rc = subp([sys.executable,"./HelloWorld.py"])
ThdSomething(testThatThing,'tests')
sys.exit(app.exec_())
使用 HelloWorld.py 文件:
#!/usr/bin/env python3
import sys
if __name__=='__main__':
print('HelloWorld')
sys.exit(0)
因此我有两个问题:如何使这个示例与 QThread 正常工作?为什么之前调用异步任务(调用subp 函数)会改变Linux 上示例的稳定性?
编辑
根据@user4815162342 的建议,我尝试使用run_coroutine_threadsafe 使用下面的代码。但它不起作用并返回相同的错误,即RuntimeError: Cannot add child handler, the child watcher does not have a loop attached。我还尝试通过模块 mutliprocessing 中的等效命令来更改 threading 命令;最后一个,命令subp 永远不会启动。
代码:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
## IMPORTS ##
import sys
import asyncio.subprocess
import threading
import multiprocessing
# at top-level
loop = asyncio.new_event_loop()
def spin_loop():
asyncio.set_event_loop(loop)
loop.run_forever()
def subp(cmd_list):
# submit the task to asyncio
fut = asyncio.run_coroutine_threadsafe(get_subp(cmd_list), loop)
# wait for the task to finish
rc, stdout, stderr = fut.result()
return rc, stdout, stderr
async def get_subp(cmd_list):
""" """
print('subp: '+' '.join(cmd_list) )
# Create the subprocess, redirect the standard output into a pipe
proc = await asyncio.create_subprocess_exec(*cmd_list, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) #
# read child's stdout/stderr concurrently (capture and display)
try:
stdout, stderr = await asyncio.gather(
read_stream_and_display(proc.stdout),
read_stream_and_display(proc.stderr))
except Exception:
proc.kill()
raise
finally:
rc = await proc.wait()
print(" [Exit {}] ".format(rc)+' '.join(cmd_list))
return rc, stdout, stderr
async def read_stream_and_display(stream):
""" """
async for line in stream:
print(line, flush=True)
if __name__=='__main__':
threading.Thread(target=spin_loop, daemon=True).start()
# multiprocessing.Process(target=spin_loop, daemon=True).start()
print('thread passed')
rc = subp([sys.executable,"./HelloWorld.py"])
print('end')
sys.exit(0)
【问题讨论】:
-
作为一般设计原则,创建新的事件循环只是为了运行单个子例程是不必要和浪费的。相反,创建一个事件循环并在单独的线程中运行它。单个事件循环完全有能力同时为多个请求提供服务——事实上,这正是它所擅长的。使用
asyncio.run_coroutine_threadsafe向事件循环提交协程,并使用result()方法等待协程完成。 (您也可以使用add_done_callback在结果可用时收到通知,在这种情况下,您可能需要线程开始。) -
@user4815162342 你认为这能解决问题吗?
-
@user4815162342 否则,我同意你的原则,即使我在这里应用它会遇到一些麻烦。
-
是的,我认为这会解决问题。我无法证明这一点,因为我无法轻松运行您的代码,并且我不想将其发布为答案,因为它不能直接解决您的问题。
-
我尝试使用
run_coroutine_threadsafe编辑我的问题。
标签: python-3.x multithreading pyqt5 python-asyncio