Гонка двух задач в разных циклах событий - PullRequest
0 голосов
/ 27 января 2019

Я использую Docker SDK и пытаюсь состязаться с заданием, которое истекает через несколько секунд, с другим заданием, ожидающим завершения в контейнере Docker.По сути, я хочу знать, завершится ли данный контейнер в течение установленного времени ожидания.

У меня есть следующий код, чтобы сделать это (адаптировано из этого поста ):

container =  # ... create container with Docker SDK
timeout =  # ... some int
killed = None

# our tasks
async def __timeout():
  await asyncio.sleep(timeout)
  return True
async def __run():
  container.wait()
  return False

# loop and runner
wait_loop = asyncio.new_event_loop()
done, pending = wait_loop.run_until_complete(
  asyncio.wait({__run(), __timeout()}, return_when=asyncio.FIRST_COMPLETED)
)

# result extraction
for task in done:
  if killed is None:
    killed = task.result()
    # ... do something with result

# clean up
for task in pending:
  task.cancel()
  with contextlib.suppress(asyncio.CancelledError):
    wait_loop.run_until_complete(task)
wait_loop.close()

К сожалению, я продолжаю получать следующую ошибку:

  File "/usr/lib/python3.5/asyncio/base_events.py", line 387, in run_until_complete
    return future.result()
  File "/usr/lib/python3.5/asyncio/futures.py", line 274, in result
    raise self._exception
  File "/usr/lib/python3.5/asyncio/tasks.py", line 241, in _step
    result = coro.throw(exc)
  File "/usr/lib/python3.5/asyncio/tasks.py", line 347, in wait
    return (yield from _wait(fs, timeout, return_when, loop))
  File "/usr/lib/python3.5/asyncio/tasks.py", line 430, in _wait
    yield from waiter
  File "/usr/lib/python3.5/asyncio/futures.py", line 361, in __iter__
    yield self  # This tells Task to wait for completion.
RuntimeError: Task <Task pending coro=<wait() running at /usr/lib/python3.5/asyncio/tasks.py:347> cb=[_run_until_complete_cb() at /usr/lib/python3.5/asyncio/base_events.py:164]> got Future <Future> pending> attached to a different loop

Кажется, я не могу поспеть с задачей ожидания, потому что она принадлежит другому циклу.Можно ли как-то обойти эту ошибку, чтобы я мог определить, какая задача завершается первой?

1 Ответ

0 голосов
/ 27 января 2019

Проблема проста, в каждом потоке есть один цикл по умолчанию.Который установлен asyncio.set_event_loop(loop).Затем вы можете получить этот цикл с помощью loop = asyncio.get_event_loop().

Так что проблема, в основном, в том, что некоторые пакеты по умолчанию используют asyncio.get_event_loop() для получения текущего запущенного цикла.Возьмем aiohttp в качестве примера:

class aiohttp.ClientSession(*, connector=None, loop=None, cookies=None, headers=None, skip_auto_headers=None, auth=None, json_serialize=json.dumps, version=aiohttp.HttpVersion11, cookie_jar=None, read_timeout=None, conn_timeout=None, timeout=sentinel, raise_for_status=False, connector_owner=True, auto_decompress=True, requote_redirect_url=False, trust_env=False, trace_configs=None)

Как вы можете видеть, он принимает параметр loop для указания работающего цикла.Но вы также можете просто оставить его пустым, чтобы использовать asyncio.get_event_loop() по умолчанию.

Ваша проблема в том, что вы запускаете сопрограммы в новом созданном цикле.Но вы не можете подтвердить, что все ваши внутренние операции также используют эту новую созданную.Поскольку они могут использовать asyncio.get_event_loop(), они будут присоединены к другому циклу, который является циклом по умолчанию в текущем потоке.


Насколько я думаю, вам не нужно создавать новый, но пусть это делают пользователи.Как и в примере выше, вы принимаете аргумент loop, а если он равен None, используйте аргумент по умолчанию.


Или вам нужно тщательно проверить свой код, чтобы убедиться, что все возможные сопрограммыиспользует созданный вами цикл.

...