如何在windows使用asyncio调用子进程
尝试在win环境运行python3的asyncio.create_subprocess_shell,
也就是希望能够以协程异步的形式,运行一个子进程。
希望不仅能够获得进程id,而且主进程可以在等待子进程完成的时候,让出cpu时间。
但是,初步的代码,没法运行,会报错:
# run.py
# coding=utf-8
import time
while 1:
print(1)
time.sleep(10)
# start.py
# coding=utf-8
import asyncio
import time
async def start():
proc = await asyncio.create_subprocess_shell("python .\run.py",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
print(proc.pid)
await proc.wait()
return proc.pid
if __name__ == "__main__":
asyncio.run(start())
报错信息:
Traceback (most recent call last):
File ".\start.py", line 35, in <module>
asyncio.run(start())
File "C:\Users\linyc\AppData\Local\Programs\Python\Python37\lib\asyncio\runners.py", line 43, in run
return loop.run_until_complete(main)
File "C:\Users\linyc\AppData\Local\Programs\Python\Python37\lib\asyncio\base_events.py", line 587, in run_until_complete
return future.result()
File ".\start.py", line 14, in start
stderr=asyncio.subprocess.PIPE)
File "C:\Users\linyc\AppData\Local\Programs\Python\Python37\lib\asyncio\subprocess.py", line 202, in create_subprocess_shell
stderr=stderr, **kwds)
File "C:\Users\linyc\AppData\Local\Programs\Python\Python37\lib\asyncio\base_events.py", line 1514, in subprocess_shell
protocol, cmd, True, stdin, stdout, stderr, bufsize, **kwargs)
File "C:\Users\linyc\AppData\Local\Programs\Python\Python37\lib\asyncio\base_events.py", line 462, in _make_subprocess_transport
raise NotImplementedError
NotImplementedError
于是去查了下官网文档
https://docs.python.org/3.7/library/asyncio-subprocess.html
尝试搜关键词“win”,还真的有描述:
Note The default asyncio event loop implementation on Windows does not support subprocesses. Subprocesses are available for Windows if a ProactorEventLoop is used. See Subprocess Support on Windows for details.
这个文档,翻到最后面其实可以看到官方示例:
import asyncio
import sys
async def get_date():
code = 'import datetime; print(datetime.datetime.now())'
# Create the subprocess; redirect the standard output
# into a pipe.
proc = await asyncio.create_subprocess_exec(
sys.executable, '-c', code,
stdout=asyncio.subprocess.PIPE)
# Read one line of output.
data = await proc.stdout.readline()
line = data.decode('ascii').rstrip()
# Wait for the subprocess exit.
await proc.wait()
return line
if sys.platform == "win32":
asyncio.set_event_loop_policy(
asyncio.WindowsProactorEventLoopPolicy())
date = asyncio.run(get_date())
print(f"Current date: {date}")
继续顺着原文档里的链接摸过去: https://docs.python.org/3.7/library/asyncio-platforms.html#asyncio-windows-subprocess
也可以看到这个 WindowsProactorEventLoopPolicy 的具体描述
SelectorEventLoop on Windows does not support subproceses. On Windows, ProactorEventLoop should be used instead
懂了,那就替换掉event_loop_policy呗,改成这样:
# coding=utf-8
import asyncio
import time
import sys
if sys.platform == "win32":
asyncio.set_event_loop_policy(
asyncio.WindowsProactorEventLoopPolicy())
async def start():
proc = await asyncio.create_subprocess_shell("python .\run.py",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
print(proc.pid)
await proc.wait()
return proc.pid
if __name__ == "__main__":
asyncio.run(start())
奇怪,还是不对!
执行了下,立即就退出了,没有报错。
我写的run.py明明是一个死循环来着,期待的状态应该是一直处在等待中才对。
猜测是子进程执行报错了。
仔细看了下,才发现,“.\run.py” 不能这么写!
虽然windows powershell里确实是“python .\run.py”这样执行的。
但是python里,反斜杆会转义掉的,
其实,只需要直接“python run.py”就可以了。
于是改成这样:
# coding=utf-8
import asyncio
import time
import sys
if sys.platform == "win32":
asyncio.set_event_loop_policy(
asyncio.WindowsProactorEventLoopPolicy())
async def start():
proc = await asyncio.create_subprocess_shell("python run.py",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
print(proc.pid)
await proc.wait()
return proc.pid
if __name__ == "__main__":
asyncio.run(start())
会得到一个进程号的打印。拿到这个进程号就可以点对点杀掉这个子进程,子进程没了后这个主进程也会退出。
注意!
在windows powershell那里,尽量使用这个命令来查找某个进程号对应的进程
tasklist|findstr 一个进程号
而不是使用这个
Get-process|findstr 一个进程号
因为proc.pid这个进程号是tasklist里的进程号,Get-process里的进程号完全对不上号的!
包括如果要终止这个子进程,也必须使用配套的命令才行:
taskkill /t /f /pid 一个进程号
附1:tasklist的返回示例
PS C:\Users\linyc\Desktop> tasklist
映像名称 PID 会话名 会话# 内存使用
========================= ======== ================ =========== ============
System Idle Process 0 Services 0 8 K
System 4 Services 0 28 K
Registry 96 Services 0 23,860 K
附2:Get-process的返回示例
PS C:\Users\linyc\Desktop> get-process
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
207 17 3984 5620 0.66 8020 1 AeXAgentUIHost
568 34 16704 7908 3048 0 AeXNSAgent
412 26 20648 3260 0.83 8876 1 ApplicationFrameHost
ps命令在powershell这里能用,结果和Get-process一样。
PS C:\Users\linyc\Desktop> ps
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
211 17 4016 5636 0.66 8020 1 AeXAgentUIHost
571 34 16704 8104 3048 0 AeXNSAgent
412 26 20648 3260 0.83 8876 1 ApplicationFrameHost