[英]Running Two asyncio Coroutines, Each in its own Python Process
如果我們有 2 個asyncio
協程,是否可以使用 Python multiproessing
進程讓它們在自己的進程中運行,並允許在用戶按下 Ctrl + C時停止兩個進程中的協程(通過調用它們的stop
方法)?
這將類似於下面的代碼,除了foo.start()
和bar.start()
協程應該有自己的進程。
from builtins import KeyboardInterrupt
import asyncio
import multiprocessing
import signal
class App:
def __init__(self, text):
self.text = text
async def start(self):
self.loop_task = asyncio.create_task(self.hello())
await asyncio.wait([self.loop_task])
async def stop(self):
self.loop_task.cancel()
async def hello(self):
while True:
print(self.text)
await asyncio.sleep(2)
if __name__ == '__main__':
foo = App('foo')
bar = App('bar')
# Running in a single process works fine
try:
asyncio.run(asyncio.wait([foo.start(), bar.start()]))
except KeyboardInterrupt:
asyncio.run(asyncio.wait([foo.stop(), bar.stop()]))
嘗試使用multiprocessing
和signals
,但我也不確定如何在 2 個進程終止之前調用foo.stop()
和bar.stop()
。
if __name__ == '__main__':
def init_worker():
signal.signal(signal.SIGINT, signal.SIG_IGN)
def start_foo():
asyncio.run(foo.start())
def start_bar():
asyncio.run(bar.start())
foo = App('foo')
bar = App('bar')
pool = multiprocessing.Pool(10, init_worker)
try:
print('Starting 2 jobs')
pool.apply_async(start_foo)
pool.apply_async(start_bar)
while True:
time.sleep(1) # is sleeping like this a bad thing?
except KeyboardInterrupt:
print('Caught KeyboardInterrupt, terminating workers')
pool.terminate()
pool.join()
print('Shut down complete')
# Based on https://stackoverflow.com/a/11312948/741099
在 Ubuntu 20.04 上使用 Python 3.9.5
基於@Will Da Silva 的解決方案,我做了微小的修改以檢查是否在按Ctrl+C時調用了asyncio.run(app.stop())
class App:
def __init__(self, text):
self.text = text
async def start(self):
self.loop_task = asyncio.create_task(self.hello())
await asyncio.wait([self.loop_task])
async def stop(self):
self.loop_task.cancel()
print(f'Stopping {self.text}')
async def hello(self):
while True:
print(self.text)
await asyncio.sleep(2)
def f(app):
try:
asyncio.run(app.start())
except KeyboardInterrupt:
asyncio.run(app.stop())
if __name__ == '__main__':
jobs = (App('foo'), App('bar'))
with multiprocessing.Pool(min(len(jobs), os.cpu_count())) as pool:
try:
print(f'Starting {len(jobs)} jobs')
pool.map(f, jobs)
except KeyboardInterrupt:
print('Caught KeyboardInterrupt, terminating workers')
print('Shut down complete')
但是,如果我多次重復啟動和停止 Python 腳本, app.stop()
中print(f'Stopping {self.text}')
似乎有一半時間不會打印到標准輸出。
Output:
$ python test.py
Starting 2 jobs
bar
foo
^CCaught KeyboardInterrupt, terminating workers
Shut down complete
$ python test.py
Starting 2 jobs
bar
foo
^CCaught KeyboardInterrupt, terminating workers
Stopping bar
Shut down complete
$ python test.py
Starting 2 jobs
foo
bar
^CCaught KeyboardInterrupt, terminating workers
Stopping bar
Stopping foo
Shut down complete
這是一種在不干擾信號且不更改App
class 的情況下執行此操作的方法:
import asyncio
import multiprocessing
import os
class App:
def __init__(self, text):
self.text = text
async def start(self):
self.loop_task = asyncio.create_task(self.hello())
await asyncio.wait([self.loop_task])
async def stop(self):
self.loop_task.cancel()
async def hello(self):
while True:
print(self.text)
await asyncio.sleep(2)
def f(text):
app = App(text)
try:
asyncio.run(app.start())
except KeyboardInterrupt:
asyncio.run(app.stop())
if __name__ == '__main__':
jobs = ('foo', 'bar')
with multiprocessing.Pool(min(len(jobs), os.cpu_count())) as pool:
try:
pool.map(f, jobs)
except KeyboardInterrupt:
pool.close()
pool.join()
重要的是我們將池中的進程數限制為min(len(jobs), os.cpu_count())
,因為任何未分配的工作人員都不會在您輸入 ctrl-c 時捕獲KeyboardInterrupt
的 try-except 塊中,所以他們會引發異常。
為了完全避免這個問題,您可以為池提供一個忽略SIGINT
的初始化程序,但這也會阻止我們用KeyboardInterrupt
捕獲它。 我不確定如何只在未初始化的工作進程中忽略它。
您還可以在父進程中創建App
實例,只要它可以被 pickle 以跨越進程邊界傳遞到子進程中。
def f(app):
try:
asyncio.run(app.start())
except KeyboardInterrupt:
asyncio.run(app.stop())
if __name__ == '__main__':
jobs = (App('foo'), App('bar'))
with multiprocessing.Pool(min(len(jobs), os.cpu_count())) as pool:
try:
pool.map(f, jobs)
except KeyboardInterrupt:
pool.close()
pool.join()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.