简体   繁体   English

python asyncio从3.4迁移到3.5+

[英]python asyncio migrate from 3.4 to 3.5+

Good evening everybody, I am trying to create internet robot and I met the problem while migrating my script from python 3.4 to 3.5 or 3.6+. 大家晚上好,我试图创建互联网机器人,但在将脚本从python 3.4迁移到3.5或3.6+时遇到了问题。 It uses asyncio and works good on 3.4 python but when I start it with python3.5+ I got error: RuntimeError: Cannot run the event loop while another loop is running 它使用asyncio并在3.4 python上运行良好,但是当我使用python3.5 +启动它时出现错误: RuntimeError: Cannot run the event loop while another loop is running

here is the code scheme: 这是代码方案:

import multiprocessing as mp
import asyncio
import concurrent.futures
import aiohttp

def create_proccesses(separate_loop_creator, coro):
    proccesses = []
    for n in range(2):
        proc = mp.Process(target=separate_loop_creator, args=(coro,))
        proc.start()
        proccesses.append(proc)
    for p in proccesses:
        p.join()

def separate_loop_creator(coro):
    sep_loop = asyncio.new_event_loop()
    asyncio.set_event_loop(sep_loop)
    tasks = [asyncio.async(coro(sep_loop)) for _ in range(100)]
    try:
        sep_loop.run_until_complete(asyncio.wait(tasks))
        sep_loop.close()
    except Exception as err:
        print(err)
        for task in tasks:
            task.cancel()
        sep_loop.close()


@asyncio.coroutine
def manager(exe, loop):
    # some calculations and start coros in several processes
    loop.run_in_executor(
        exe,
        create_proccesses,
        separate_loop_creator,
        some_coro
    )

@asyncio.coroutine
def some_work_in_mainloop():
    while True:
        print('Some server dealing with connections here...')
        yield from asyncio.sleep(1)

@asyncio.coroutine
def some_coro(loop):
    with aiohttp.ClientSession(loop=loop) as session:
        response = yield from session.get('http://google.com')
        yield from asyncio.sleep(2)
        print(response.status)

if __name__ == '__main__':
    mainloop = asyncio.get_event_loop()
    executor = concurrent.futures.ProcessPoolExecutor(5)
    asyncio.async(some_work_in_mainloop())
    asyncio.async(manager(executor, mainloop))
    try:
        mainloop.run_forever()
    finally:
        mainloop.close()

The exception raise in separate_loop_creator() coroutine and it is RuntimeError: Cannot run the event loop while another loop is running . 异常在eparate_loop_creator separate_loop_creator()协程中引发,它是RuntimeError: Cannot run the event loop while another loop is running I think it is because of changing get_event_loop() mechnics, but I do not understand what is wrong with my code. 我认为这是因为更改了get_event_loop() ,但我不明白我的代码有什么问题。

Here is what I want to do: 这是我想做的:

                       +--------------+
               +-------+other service |
    +----------+       +--------------+
    | mainloop |
    +----------+
               |     +------------+
               +-----+   executor |
                     +------+-----+
                            |
                     +------+--------+
                     |start proccess |
                     +---+-------+---+
+-----------------+      |       |      +---------------+
|start new loop   +------+       +------+ start new loop|
+--------+--------+                     +-------+-------+
         |                                      |
 +-------+-------+                       +------v-------+
 |   run coro    |                       | run coro     |
 +---------------+                       +--------------+

Here is trace which I get on python3.5.3: 这是我在python3.5.3上获得的跟踪:

Traceback (most recent call last):
  File "tst.py", line 21, in separate_loop_creator
    sep_loop.run_until_complete(asyncio.wait(tasks))
  File "/root/.pyenv/versions/3.5.3/lib/python3.5/asyncio/base_events.py", line 454, in run_until_complete
    self.run_forever()
  File "/root/.pyenv/versions/3.5.3/lib/python3.5/asyncio/base_events.py", line 411, in run_forever
    'Cannot run the event loop while another loop is running')
RuntimeError: Cannot run the event loop while another loop is running
Cannot run the event loop while another loop is running
Traceback (most recent call last):
  File "tst.py", line 21, in separate_loop_creator
    sep_loop.run_until_complete(asyncio.wait(tasks))
  File "/root/.pyenv/versions/3.5.3/lib/python3.5/asyncio/base_events.py", line 454, in run_until_complete
    self.run_forever()
  File "/root/.pyenv/versions/3.5.3/lib/python3.5/asyncio/base_events.py", line 411, in run_forever
    'Cannot run the event loop while another loop is running')
RuntimeError: Cannot run the event loop while another loop is running

Python 3.4.3 results: Python 3.4.3结果:

...
200
Some server dealing with connections here...
200
200
Some server dealing with connections here...
200
200
Some server dealing with connections here...
200
...

This is actually a bug in asyncio in CPython 3.6.0. 这实际上是CPython 3.6.0中asyncio中的错误。 There's a PR to fix this, so that 3.6.1 works as expected. 有一个PR可以解决此问题,以便3.6.1可以正常工作。

As a workaround you can add the following piece of code in your project: 解决方法是,可以在项目中添加以下代码:

import sys

if sys.version_info[:3] == (3, 6, 0):
    import asyncio.events as _ae
    import os as _os

    _ae._RunningLoop._pid = None

    def _get_running_loop():
        if _ae._running_loop._pid == _os.getpid():
            return _ae._running_loop._loop

    def _set_running_loop(loop):
        _ae._running_loop._pid = _os.getpid()
        _ae._running_loop._loop = loop

    _ae._get_running_loop = _get_running_loop
    _ae._set_running_loop = _set_running_loop

The best solution, if possible, try removing multiprocessing altogether from your program, and use only one event loop (Optionally using ProcessPoolExecutor for isolated CPU intensive tasks). 最好的解决方案,如果可能,请尝试从multiprocessing完全删除multiprocessing ,并仅使用一个事件循环(对于独立的CPU密集型任务,可以选择使用ProcessPoolExecutor )。

As of 2017-03-02, there is an open python bug for this issue, effecting non-windows platforms: https://bugs.python.org/issue22087 . 截至2017年3月2日,此问题有一个打开的python错误,影响到非Windows平台: https : //bugs.python.org/issue22087

Here is a shorter program to trigger the same problem: 这是触发相同问题的较短程序:

import asyncio
import multiprocessing as mp


def create_another_loop():
    loop = asyncio.new_event_loop()
    loop.run_forever()


async def create_process():
    proc = mp.Process(target=create_another_loop)
    proc.start()
    proc.join()


if __name__ == '__main__':
    main_loop = asyncio.get_event_loop()
    main_loop.run_until_complete(create_process())
    main_loop.close()

A hackish workaround (Caution! Use at your own risk!) inspired from the fix suggested here https://github.com/python/asyncio/pull/497 is to add this code to a newly created Process : https://github.com/python/asyncio/pull/497此处建议的修复启发,一个变黑的解决方法(警告!使用后果自负!)是将此代码添加到新创建的Process

if asyncio.events._running_loop:
    asyncio.events._running_loop._loop = None

Example: 例:

import asyncio
import multiprocessing as mp
import time
from concurrent.futures.process import ProcessPoolExecutor


async def clock(label, n=5, sleep=1):
    print(label, "start")
    for i in range(n):
        await asyncio.sleep(sleep)
        print(label, i + 1)
    print(label, "end")
    return label


def create_another_loop():
    # HACK START
    if asyncio.events._running_loop:
        asyncio.events._running_loop._loop = None
    # HACK END

    loop = asyncio.new_event_loop()
    loop.run_until_complete(clock("sub"))
    loop.close()


def create_process():
    time.sleep(2)
    proc = mp.Process(target=create_another_loop)
    proc.start()
    proc.join()
    return "ok"


async def create_process_in_pool():
    return await main_loop.run_in_executor(ProcessPoolExecutor(), create_process)


if __name__ == '__main__':
    main_loop = asyncio.get_event_loop()
    tasks = (
        clock("main"),
        create_process_in_pool(),
    )
    print(main_loop.run_until_complete(asyncio.gather(*tasks)))
    main_loop.close()

Other possible workarounds: Create the processes before starting the loop or use asyncio.create_subprocess_exec which even allows to communicate with the subprocess via a stream . 其他可能的解决方法:在开始循环之前创建进程,或使用asyncio.create_subprocess_exec甚至允许通过流与子进程进行通信

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM