为什么在Windows上使用async和await时会出现NotImplementedError错误?

21
我有这段代码:
import os
import time
import asyncio


async def run_command(*args):
    """
    Example from:
        http://asyncio.readthedocs.io/en/latest/subprocess.html
    """
    # Create subprocess
    process = await asyncio.create_subprocess_exec(
        *args,
        # stdout must a pipe to be accessible as process.stdout
        stdout=asyncio.subprocess.PIPE)

    # Wait for the subprocess to finish
    stdout, stderr = await process.communicate()

    # Result
    result = stdout.decode().strip()

    # Return stdout
    return result


def run_asyncio_commands(tasks):
    """Run tasks using asyncio and return results"""
    loop = asyncio.get_event_loop()
    commands = asyncio.gather(*tasks)  # Unpack list using *
    results = loop.run_until_complete(commands)
    loop.close()
    return results


if __name__ == '__main__':

    start = time.time()

    cmds = [
        ['du', '-sh', '/Users/fredrik/Desktop'],
        ['du', '-sh', '/Users/fredrik'],
        ['du', '-sh', '/Users/fredrik/Pictures']
    ]

    tasks = []
    for cmd in cmds:
        tasks.append(run_command(*cmd))
    results = run_asyncio_commands(tasks)
    print(results)

    end = time.time()
    print('Script ran in', str(end - start), 'seconds')

当我在我的Mac上运行Python 3.6.1中的那段代码时,我得到了这个结果:
['780K\t/Users/fredrik/Desktop', '46G\t/Users/fredrik', '52M\t/Users/fredrik/Pictures']
Script ran in 6.405519008636475 seconds

但是当我在Windows上运行相同的脚本时(但是将du命令替换为在Windows上有效的内容),同时使用Python 3.6.1,我得到了这个:

Traceback (most recent call last):
  File "C:\Users\iruser\Desktop\asynciotest.py", line 66, in <module>
    results = run_asyncio_commands(tasks)
  File "C:\Users\iruser\Desktop\asynciotest.py", line 41, in run_asyncio_commands
    results = loop.run_until_complete(commands)
  File "C:\Users\fredrik\condaenvs\dev_py36\lib\asyncio\base_events.py", line 466, in run_until_complete
    return future.result()
  File "C:\Users\iruser\Desktop\asynciotest.py", line 16, in run_command
    stdout=asyncio.subprocess.PIPE)
  File "C:\Users\fredrik\condaenvs\dev_py36\lib\asyncio\subprocess.py", line 225, in create_subprocess_exec
    stderr=stderr, **kwds)
  File "C:\Users\fredrik\condaenvs\dev_py36\lib\asyncio\base_events.py", line 1190, in subprocess_exec
    bufsize, **kwargs)
  File "C:\Users\fredrik\condaenvs\dev_py36\lib\asyncio\coroutines.py", line 210, in coro
    res = func(*args, **kw)
  File "C:\Users\fredrik\condaenvs\dev_py36\lib\asyncio\base_events.py", line 340, in _make_subprocess_transp
ort
    raise NotImplementedError
NotImplementedError

这是我在Windows上用来替代Unix命令的方法:
cmds = [['C:/Windows/system32/HOSTNAME.EXE']]

Python和Windows版本信息:

Python 3.6.1 | packaged by conda-forge | (default, May 23 2017, 14:21:39) [MSC v.1900 64 bit (AMD64)] on win32
Windows 10 Pro, version 1703, OS build 15063.413
5个回答

23

不同的事件循环实现方式不同。其中一些具有限制(有时与操作系统相关)。默认情况下,Windows 使用SelectorEventLoop,如文档中所述:

SelectorEventLoop 有以下限制:

  • 使用 SelectSelector 等待套接字事件:它支持套接字并限制为 512 个套接字。
  • loop.add_reader() 和 loop.add_writer() 仅接受套接字句柄(例如,不支持管道文件描述符)。
  • 不支持 Pipes,因此未实现 loop.connect_read_pipe() 和 loop.connect_write_pipe() 方法。
  • 不支持子进程,即未实现 loop.subprocess_exec() 和 loop.subprocess_shell() 方法。

要在 Windows 上运行代码,可以使用默认提供的替代事件循环 - ProactorEventLoop

请将以下行替换为:

loop = asyncio.get_event_loop()

使用这个:

loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)

你的代码将会工作。


通过添加:asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) - Jordan Stefanelli
我该如何在使用pytest和pytest-asyncio运行测试时使用ProactorEventLoop? - Brent
在我上面的评论中的问题@ pytest.fixture \n def event_loop(request): \n loop = asyncio.ProactorEventLoop() \n yield loop \n loop.close() - Brent
@Brent 你可能需要创建一个自定义的 event_loop。可以在这里看一下 - https://github.com/pytest-dev/pytest-asyncio#event_loop - Mikhail Gerasimov
2
这对我没有用 - 我添加了解决方案中的两行以及Jordan的策略,但仍然显示loop.add_readerNotImplementedError - Flying Thunder

6

3.7.0 Python文档在此处处理:

https://docs.python.org/zh-cn/3/library/asyncio-platforms.html#asyncio-windows-subprocess

如果您使用Windows,请设置事件循环策略,然后您的代码将能够工作。

在启动时,更改Unix特定部分:

cmds = [
    ['du', '-sh', '/Users/fredrik/Desktop'],
    ['du', '-sh', '/Users/fredrik'],
    ['du', '-sh', '/Users/fredrik/Pictures']
]

处理Windows和Unix:

if 'win32' in sys.platform:
    # Windows specific event-loop policy & cmd
    asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
    cmds = [['C:/Windows/system32/HOSTNAME.EXE']]
else:
    # Unix default event-loop policy & cmds
    cmds = [
        ['du', '-sh', '/Users/fredrik/Desktop'],
        ['du', '-sh', '/Users/fredrik'],
        ['du', '-sh', '/Users/fredrik/Pictures']
    ]

请注意,[WindowsSelectorEventLoopPolicyWindowsProactorEventLoopPolicy]是在Python 3.7中首次添加的(https://bugs.python.org/issue33792),因此设置默认策略将无法在Python 3.6或更早版本中使用。 - hoefling

2
这个有一个GitHub的问题页面[https://github.com/saghul/aiodns/issues/86][1]
最新的添加是建议添加以下内容:
if sys.platform == 'win32':
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

希望这对某人有所帮助。

1
这个对我解决了问题。谢谢。 - undefined

0

我在Python和IPython中进行了测试,似乎可以在Python中更改循环,但无法在IPython中更改。我还发现Jupyter的默认循环是“`”,也无法更改。

在Python中的测试

C:\Users\Liu.D.H>python
Python 3.10.6 (tags/v3.10.6:9c7b4bd, Aug  1 2022, 21:53:49) [MSC v.1932 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> asyncio.get_event_loop_policy()
<asyncio.windows_events.WindowsProactorEventLoopPolicy object at 0x0000018054ACC790>
>>> asyncio.get_event_loop()
<stdin>:1: DeprecationWarning: There is no current event loop
<ProactorEventLoop running=False closed=False debug=False>
>>> asyncio.get_event_loop()
<ProactorEventLoop running=False closed=False debug=False>
>>> asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
>>> asyncio.get_event_loop_policy()
<asyncio.windows_events.WindowsSelectorEventLoopPolicy object at 0x0000018054A8E0E0>
>>> asyncio.get_event_loop()
<_WindowsSelectorEventLoop running=False closed=False debug=False>
>>> exit()

C:\Users\Liu.D.H>

ipython 中的测试

C:\Users\Liu.D.H>ipython
Python 3.10.6 (tags/v3.10.6:9c7b4bd, Aug  1 2022, 21:53:49) [MSC v.1932 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.5.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import asyncio

In [2]: asyncio.get_event_loop_policy()
Out[2]: <asyncio.windows_events.WindowsProactorEventLoopPolicy at 0x1df598b7e20>

In [3]: asyncio.get_event_loop()
<ipython-input-3-6908e23590ee>:1: DeprecationWarning: There is no current event loop
  asyncio.get_event_loop()
Out[3]: <ProactorEventLoop running=False closed=False debug=False>

In [4]: asyncio.get_event_loop()
<ipython-input-4-6908e23590ee>:1: DeprecationWarning: There is no current event loop
  asyncio.get_event_loop()
Out[4]: <ProactorEventLoop running=False closed=False debug=False>

In [5]: asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

In [6]: asyncio.get_event_loop_policy()
Out[6]: <asyncio.windows_events.WindowsSelectorEventLoopPolicy at 0x1df59a6c820>

In [7]: asyncio.get_event_loop()
<ipython-input-7-6908e23590ee>:1: DeprecationWarning: There is no current event loop
  asyncio.get_event_loop()
Out[7]: <ProactorEventLoop running=False closed=False debug=False>

In [8]: asyncio.get_event_loop()
<ipython-input-8-6908e23590ee>:1: DeprecationWarning: There is no current event loop
  asyncio.get_event_loop()
Out[8]: <ProactorEventLoop running=False closed=False debug=False>

In [9]: asyncio.set_event_loop(asyncio.SelectorEventLoop())

In [10]: asyncio.get_event_loop()
<ipython-input-10-6908e23590ee>:1: DeprecationWarning: There is no current event loop
  asyncio.get_event_loop()
Out[10]: <ProactorEventLoop running=False closed=False debug=False>

In [11]:

0
在Windows上使用Jupyter时,它使用"SelectorEventLoop"来使asyncio.subprocess正常工作,但是没有办法做到这一点。
自2019年以来,有一个GitHub的问题 (2)一直未解决,而且可能不会很快得到修复(1) (2)
然而,我成功地使用subprocess.Popen从jupyter异步运行了一个docker进程。
import asyncio
import subprocess


async def run_docker(args: list[str]):
    def run():
        process = subprocess.Popen(
            ["docker"]
            + args,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
        )

        stdout, stderr = process.communicate()

        if stderr:
            raise Exception(stderr)

        return stdout

    return await asyncio.to_thread(run)

就个人而言,我觉得很失望的是,到了2023年,Python生态系统仍然缺乏一种强大且被广泛采用的异步处理方法。即使JavaScript在很久以前就解决了这个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接