زیرفرایندها

کد منبع: Lib/asyncio/subprocess.py, Lib/asyncio/base_subprocess.py


این بخش APIهای سطح‌بالای async/await asyncio را برای ایجاد و مدیریت زیرفرایندها توضیح می‌دهد.

در اینجا مثالی از این آمده است که asyncio چگونه می‌تواند یک دستور پوسته را اجرا کند و نتیجه‌ی آن را به دست آورد:

import asyncio

async def run(cmd):
    proc = await asyncio.create_subprocess_shell(
        cmd,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE)

    stdout, stderr = await proc.communicate()

    print(f'[{cmd!r} exited with {proc.returncode}]')
    if stdout:
        print(f'[stdout]\n{stdout.decode()}')
    if stderr:
        print(f'[stderr]\n{stderr.decode()}')

asyncio.run(run('ls /zzz'))

چاپ خواهد شد:

['ls /zzz' exited with 1]
[stderr]
ls: /zzz: No such file or directory

از آن‌جا که تمام توابع زیرفرایند در asyncio ناهمگام هستند و asyncio ابزارهای بسیاری برای کار با چنین توابعی فراهم می‌کند، اجرا و پایش چندین زیرفرایند به‌صورت موازی آسان است. در واقع، اصلاح مثال بالا برای اجرای همزمان چندین فرمان بسیار ساده است:

async def main():
    await asyncio.gather(
        run('ls /zzz'),
        run('sleep 1; echo "hello"'))

asyncio.run(main())

همچنین زیربخش Examples را ببینید.

ایجاد زیرفرآیندها

async asyncio.create_subprocess_exec(program, *args, stdin=None, stdout=None, stderr=None, limit=65536, **kwds)

ایجاد یک زیرفرایند.

آرگومان limit محدودیت بافر را برای دربرگیرنده‌های StreamReader مربوط به stdout و stderr تنظیم می‌کند (اگر subprocess.PIPE به آرگومان‌های stdout و stderr ارسال شود).

یک نمونه از Process برمی‌گرداند.

برای سایر پارامترها، مستندات loop.subprocess_exec() را ببینید.

اگر شیء فرایند در حالی که فرایند هنوز در حال اجرا است زباله‌روبی شود، فرایند فرزند خاتمه داده خواهد شد.

تغییر یافته در نسخه‌ی 3.10: پارامتر loop حذف شد.

async asyncio.create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None, limit=65536, **kwds)

دستور پوسته‌ی cmd را اجرا کنید.

آرگومان limit محدودیت بافر را برای دربرگیرنده‌های StreamReader مربوط به stdout و stderr تنظیم می‌کند (اگر subprocess.PIPE به آرگومان‌های stdout و stderr ارسال شود).

یک نمونه از Process برمی‌گرداند.

برای سایر پارامترها، مستندات loop.subprocess_shell() را ببینید.

اگر شیء فرایند در حالی که فرایند هنوز در حال اجرا است زباله‌روبی شود، فرایند فرزند خاتمه داده خواهد شد.

مهم

این مسئولیت برنامه است که اطمینان حاصل کند همه‌ی نویسه‌های فضای خالی و نویسه‌های خاص به‌درستی درون علامت نقل‌قول قرار گیرند تا از آسیب‌پذیری‌های تزریق پوسته جلوگیری شود. برای خنثی کردن صحیح نویسه‌های فضای خالی و نویسه‌های خاص پوسته در رشته‌هایی که قرار است برای ساخت دستورات پوسته استفاده شوند، می‌توان از تابع shlex.quote() استفاده کرد.

تغییر یافته در نسخه‌ی 3.10: پارامتر loop حذف شد.

توجه

زیرفرایندها برای ویندوز در دسترس هستند، اگر از ProactorEventLoop استفاده شود. برای جزئیات، پشتیبانی از زیرفرایند در ویندوز را ببینید.

همچنین ملاحظه نمائید

asyncio همچنین APIهای سطح پایین زیر را برای کار با زیرفرایندها دارد: loop.subprocess_exec()، loop.subprocess_shell()، loop.connect_read_pipe()، loop.connect_write_pipe()، و نیز ترابری‌های زیرفرایند و پروتکل‌های زیرفرایند.

ثابت‌ها

asyncio.subprocess.PIPE

می‌تواند به پارامترهای stdin، stdout یا stderr ارسال شود.

اگر PIPE به آرگومان stdin داده شود، ویژگی Process.stdin به نمونه‌ای از StreamWriter اشاره خواهد کرد.

اگر PIPE به آرگومان‌های stdout یا stderr داده شود، ویژگی‌های Process.stdout و Process.stderr به نمونه‌هایی از StreamReader اشاره خواهند کرد.

asyncio.subprocess.STDOUT

مقدار ویژه‌ای که می‌تواند به‌عنوان آرگومان stderr استفاده شود و نشان می‌دهد که خطای استاندارد باید به خروجی استاندارد هدایت شود.

asyncio.subprocess.DEVNULL

مقدار ویژه‌ای که می‌تواند به‌عنوان آرگومان stdin، stdout یا stderr در توابع ایجاد فرایند استفاده شود. این مقدار نشان می‌دهد که پرونده ویژه‌ی os.devnull برای جریان متناظر زیرفرایند استفاده خواهد شد.

تعامل با زیرفرایندها

هر دو تابع create_subprocess_exec() و create_subprocess_shell() نمونه‌هایی از کلاس Process را برمی‌گردانند. Process دربرگیرنده‌ای سطح بالا است که امکان ارتباط با زیرفرایندها و پایش تکمیل آن‌ها را فراهم می‌کند.

class asyncio.subprocess.Process

شیءای که فرآیندهای سیستم‌عاملِ ایجادشده توسط توابع create_subprocess_exec() و create_subprocess_shell() را در بر می‌گیرد.

این کلاس طراحی شده است تا API مشابهی با کلاس subprocess.Popen داشته باشد، اما برخی تفاوت‌های قابل‌توجه وجود دارد:

  • برخلاف Popen، نمونه‌های Process معادلی برای متد poll() ندارند؛

  • متدهای communicate() و wait() پارامتر timeout ندارند: از تابع wait_for() استفاده کنید؛

  • متد Process.wait() ناهمگام است، در حالی که متد subprocess.Popen.wait() به‌صورت یک حلقه مشغول مسدودکننده (blocking busy loop) پیاده‌سازی شده است؛

  • پارامتر universal_newlines پشتیبانی نمی‌شود.

این کلاس نخ‌ایمن نیست.

همچنین بخش زیرفرایند و نخ‌ها را ببینید.

async wait()

منتظر بمانید تا فرایند فرزند خاتمه یابد.

ویژگی returncode را تنظیم می‌کند و برمی‌گرداند.

توجه

این متد ممکن است هنگام استفاده از stdout=PIPE یا stderr=PIPE و در صورتی که فرایند فرزند آن‌قدر خروجی تولید کند که در انتظار پذیرش داده‌های بیشتر توسط بافر پایپ سیستم‌عامل مسدود شود، دچار بن‌بست شود. برای جلوگیری از این وضعیت، هنگام استفاده از پایپ‌ها از متد communicate() استفاده کنید.

async communicate(input=None)

تعامل با فرایند:

  1. ارسال داده به stdin (اگر input None نباشد)؛

  2. stdin را می‌بندد؛

  3. داده‌ها را از stdout و stderr، تا رسیدن به EOF بخوانید؛

  4. صبر کنید تا فرایند خاتمه یابد.

آرگومان اختیاری input، داده‌ای است (شیء bytes) که به فرایند فرزند ارسال خواهد شد.

یک تاپل (stdout_data, stderr_data) برمی‌گرداند.

اگر هنگام نوشتن input به stdin، استثنای BrokenPipeError یا ConnectionResetError پرتاب شود، آن استثنا نادیده گرفته می‌شود. این حالت زمانی رخ می‌دهد که فرآیند پیش از آنکه همه داده‌ها به stdin نوشته شوند، خارج شود.

اگر بخواهید داده‌ای را به stdin فرایند بفرستید، فرایند باید با stdin=PIPE ایجاد شود. به‌طور مشابه، برای دریافت هر چیزی غیر از None در تاپل نتیجه، فرایند باید با آرگومان‌های stdout=PIPE و/یا stderr=PIPE ایجاد شود.

توجه داشته باشید که داده‌های خوانده‌شده در حافظه بافر می‌شوند، بنابراین اگر اندازه‌ی داده‌ها بزرگ یا نامحدود است، از این متد استفاده نکنید.

تغییر یافته در نسخه‌ی 3.12: stdin همچنین وقتی input=None باشد بسته می‌شود.

send_signal(signal)

سیگنال signal را به فرایند فرزند ارسال می‌کند.

توجه

در ویندوز، SIGTERM نام مستعاری برای terminate() است. می‌توان CTRL_C_EVENT و CTRL_BREAK_EVENT را به فرآیندهایی که با پارامتر creationflags حاوی CREATE_NEW_PROCESS_GROUP آغاز شده‌اند، ارسال کرد.

terminate()

فرایند فرزند را متوقف کنید.

در سیستم‌های POSIX، این متد SIGTERM را به فرایند فرزند ارسال می‌کند.

در ویندوز، تابع Win32 API با نام TerminateProcess() برای توقف فرایند فرزند فراخوانی می‌شود.

kill()

فرایند فرزند را خاتمه دهید.

در سیستم‌های POSIX، این متد SIGKILL را به فرایند فرزند ارسال می‌کند.

در ویندوز، این متد نام مستعاری برای terminate() است.

stdin

جریان ورودی استاندارد (StreamWriter) یا None اگر فرایند با stdin=None ایجاد شده باشد.

stdout

جریان خروجی استاندارد (StreamReader) یا None اگر فرآیند با stdout=None ایجاد شده باشد.

stderr

جریان خطای استاندارد (StreamReader) یا None اگر فرآیند با stderr=None ایجاد شده باشد.

هشدار

به‌جای process.stdin.write()، await process.stdout.read() یا await process.stderr.read() از متد communicate() استفاده کنید. این کار از بن‌بست‌های ناشی از توقف خواندن یا نوشتن جریان‌ها و مسدود شدن فرایند فرزند جلوگیری می‌کند.

pid

شماره‌ی شناسایی فرایند (PID).

توجه داشته باشید که برای فرایندهای ایجادشده توسط تابع create_subprocess_shell()، این ویژگی، PID پوسته راه‌اندازی‌شده است.

returncode

کد بازگشتی فرایند هنگام خروج.

مقدار None نشان می‌دهد که فرآیند هنوز به پایان نرسیده است.

برای فرایندهایی که با create_subprocess_exec() ایجاد می‌شوند، مقدار منفی -N نشان می‌دهد که فرایند فرزند با سیگنال N خاتمه یافته است (فقط POSIX).

برای فرایندهایی که با create_subprocess_shell() ایجاد می‌شوند، کد بازگشتی وضعیت خروج خود پوسته (برای مثال /bin/sh) را منعکس می‌کند، که ممکن است سیگنال‌ها را به کدهایی مانند 128+N نگاشت کند. برای جزئیات، مستندات پوسته (برای مثال، بخش Exit Status دفترچه راهنمای Bash) را ببینید.

زیرفرایند و نخ‌ها

حلقه رویداد استاندارد asyncio به‌صورت پیش‌فرض از اجرای زیرفرایندها از نخ‌های مختلف پشتیبانی می‌کند.

در ویندوز، زیرفرایندها فقط توسط ProactorEventLoop (پیش‌فرض) ارائه می‌شوند؛ SelectorEventLoop از زیرفرایندها پشتیبانی نمی‌کند.

توجه داشته باشید که پیاده‌سازی‌های جایگزین حلقه رویداد ممکن است محدودیت‌های خاص خود را داشته باشند؛ لطفاً به مستندات آن‌ها مراجعه کنید.

همچنین ملاحظه نمائید

بخش هم‌روندی و چندنخی در asyncio.

مثال‌ها

مثالی که از کلاس Process برای کنترل یک زیرفرایند و از کلاس StreamReader برای خواندن از خروجی استاندارد آن استفاده می‌کند.

زیرفرایند توسط تابع create_subprocess_exec() ایجاد می‌شود:

import asyncio
import sys

async def get_date():
    code = 'import datetime as dt; print(dt.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

date = asyncio.run(get_date())
print(f"Current date: {date}")

همچنین همین مثال را که با استفاده از APIهای سطح پایین نوشته‌شده است، ببینید.