17db96d56Sopenharmony_ci.. currentmodule:: asyncio 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_ci.. _asyncio-subprocess: 47db96d56Sopenharmony_ci 57db96d56Sopenharmony_ci============ 67db96d56Sopenharmony_ciSubprocesses 77db96d56Sopenharmony_ci============ 87db96d56Sopenharmony_ci 97db96d56Sopenharmony_ci**Source code:** :source:`Lib/asyncio/subprocess.py`, 107db96d56Sopenharmony_ci:source:`Lib/asyncio/base_subprocess.py` 117db96d56Sopenharmony_ci 127db96d56Sopenharmony_ci---------------------------------------- 137db96d56Sopenharmony_ci 147db96d56Sopenharmony_ciThis section describes high-level async/await asyncio APIs to 157db96d56Sopenharmony_cicreate and manage subprocesses. 167db96d56Sopenharmony_ci 177db96d56Sopenharmony_ci.. _asyncio_example_subprocess_shell: 187db96d56Sopenharmony_ci 197db96d56Sopenharmony_ciHere's an example of how asyncio can run a shell command and 207db96d56Sopenharmony_ciobtain its result:: 217db96d56Sopenharmony_ci 227db96d56Sopenharmony_ci import asyncio 237db96d56Sopenharmony_ci 247db96d56Sopenharmony_ci async def run(cmd): 257db96d56Sopenharmony_ci proc = await asyncio.create_subprocess_shell( 267db96d56Sopenharmony_ci cmd, 277db96d56Sopenharmony_ci stdout=asyncio.subprocess.PIPE, 287db96d56Sopenharmony_ci stderr=asyncio.subprocess.PIPE) 297db96d56Sopenharmony_ci 307db96d56Sopenharmony_ci stdout, stderr = await proc.communicate() 317db96d56Sopenharmony_ci 327db96d56Sopenharmony_ci print(f'[{cmd!r} exited with {proc.returncode}]') 337db96d56Sopenharmony_ci if stdout: 347db96d56Sopenharmony_ci print(f'[stdout]\n{stdout.decode()}') 357db96d56Sopenharmony_ci if stderr: 367db96d56Sopenharmony_ci print(f'[stderr]\n{stderr.decode()}') 377db96d56Sopenharmony_ci 387db96d56Sopenharmony_ci asyncio.run(run('ls /zzz')) 397db96d56Sopenharmony_ci 407db96d56Sopenharmony_ciwill print:: 417db96d56Sopenharmony_ci 427db96d56Sopenharmony_ci ['ls /zzz' exited with 1] 437db96d56Sopenharmony_ci [stderr] 447db96d56Sopenharmony_ci ls: /zzz: No such file or directory 457db96d56Sopenharmony_ci 467db96d56Sopenharmony_ciBecause all asyncio subprocess functions are asynchronous and asyncio 477db96d56Sopenharmony_ciprovides many tools to work with such functions, it is easy to execute 487db96d56Sopenharmony_ciand monitor multiple subprocesses in parallel. It is indeed trivial 497db96d56Sopenharmony_cito modify the above example to run several commands simultaneously:: 507db96d56Sopenharmony_ci 517db96d56Sopenharmony_ci async def main(): 527db96d56Sopenharmony_ci await asyncio.gather( 537db96d56Sopenharmony_ci run('ls /zzz'), 547db96d56Sopenharmony_ci run('sleep 1; echo "hello"')) 557db96d56Sopenharmony_ci 567db96d56Sopenharmony_ci asyncio.run(main()) 577db96d56Sopenharmony_ci 587db96d56Sopenharmony_ciSee also the `Examples`_ subsection. 597db96d56Sopenharmony_ci 607db96d56Sopenharmony_ci 617db96d56Sopenharmony_ciCreating Subprocesses 627db96d56Sopenharmony_ci===================== 637db96d56Sopenharmony_ci 647db96d56Sopenharmony_ci.. coroutinefunction:: create_subprocess_exec(program, *args, stdin=None, \ 657db96d56Sopenharmony_ci stdout=None, stderr=None, limit=None, **kwds) 667db96d56Sopenharmony_ci 677db96d56Sopenharmony_ci Create a subprocess. 687db96d56Sopenharmony_ci 697db96d56Sopenharmony_ci The *limit* argument sets the buffer limit for :class:`StreamReader` 707db96d56Sopenharmony_ci wrappers for :attr:`Process.stdout` and :attr:`Process.stderr` 717db96d56Sopenharmony_ci (if :attr:`subprocess.PIPE` is passed to *stdout* and *stderr* arguments). 727db96d56Sopenharmony_ci 737db96d56Sopenharmony_ci Return a :class:`~asyncio.subprocess.Process` instance. 747db96d56Sopenharmony_ci 757db96d56Sopenharmony_ci See the documentation of :meth:`loop.subprocess_exec` for other 767db96d56Sopenharmony_ci parameters. 777db96d56Sopenharmony_ci 787db96d56Sopenharmony_ci .. versionchanged:: 3.10 797db96d56Sopenharmony_ci Removed the *loop* parameter. 807db96d56Sopenharmony_ci 817db96d56Sopenharmony_ci 827db96d56Sopenharmony_ci.. coroutinefunction:: create_subprocess_shell(cmd, stdin=None, \ 837db96d56Sopenharmony_ci stdout=None, stderr=None, limit=None, **kwds) 847db96d56Sopenharmony_ci 857db96d56Sopenharmony_ci Run the *cmd* shell command. 867db96d56Sopenharmony_ci 877db96d56Sopenharmony_ci The *limit* argument sets the buffer limit for :class:`StreamReader` 887db96d56Sopenharmony_ci wrappers for :attr:`Process.stdout` and :attr:`Process.stderr` 897db96d56Sopenharmony_ci (if :attr:`subprocess.PIPE` is passed to *stdout* and *stderr* arguments). 907db96d56Sopenharmony_ci 917db96d56Sopenharmony_ci Return a :class:`~asyncio.subprocess.Process` instance. 927db96d56Sopenharmony_ci 937db96d56Sopenharmony_ci See the documentation of :meth:`loop.subprocess_shell` for other 947db96d56Sopenharmony_ci parameters. 957db96d56Sopenharmony_ci 967db96d56Sopenharmony_ci .. important:: 977db96d56Sopenharmony_ci 987db96d56Sopenharmony_ci It is the application's responsibility to ensure that all whitespace and 997db96d56Sopenharmony_ci special characters are quoted appropriately to avoid `shell injection 1007db96d56Sopenharmony_ci <https://en.wikipedia.org/wiki/Shell_injection#Shell_injection>`_ 1017db96d56Sopenharmony_ci vulnerabilities. The :func:`shlex.quote` function can be used to properly 1027db96d56Sopenharmony_ci escape whitespace and special shell characters in strings that are going 1037db96d56Sopenharmony_ci to be used to construct shell commands. 1047db96d56Sopenharmony_ci 1057db96d56Sopenharmony_ci .. versionchanged:: 3.10 1067db96d56Sopenharmony_ci Removed the *loop* parameter. 1077db96d56Sopenharmony_ci 1087db96d56Sopenharmony_ci.. note:: 1097db96d56Sopenharmony_ci 1107db96d56Sopenharmony_ci Subprocesses are available for Windows if a :class:`ProactorEventLoop` is 1117db96d56Sopenharmony_ci used. See :ref:`Subprocess Support on Windows <asyncio-windows-subprocess>` 1127db96d56Sopenharmony_ci for details. 1137db96d56Sopenharmony_ci 1147db96d56Sopenharmony_ci.. seealso:: 1157db96d56Sopenharmony_ci 1167db96d56Sopenharmony_ci asyncio also has the following *low-level* APIs to work with subprocesses: 1177db96d56Sopenharmony_ci :meth:`loop.subprocess_exec`, :meth:`loop.subprocess_shell`, 1187db96d56Sopenharmony_ci :meth:`loop.connect_read_pipe`, :meth:`loop.connect_write_pipe`, 1197db96d56Sopenharmony_ci as well as the :ref:`Subprocess Transports <asyncio-subprocess-transports>` 1207db96d56Sopenharmony_ci and :ref:`Subprocess Protocols <asyncio-subprocess-protocols>`. 1217db96d56Sopenharmony_ci 1227db96d56Sopenharmony_ci 1237db96d56Sopenharmony_ciConstants 1247db96d56Sopenharmony_ci========= 1257db96d56Sopenharmony_ci 1267db96d56Sopenharmony_ci.. data:: asyncio.subprocess.PIPE 1277db96d56Sopenharmony_ci :module: 1287db96d56Sopenharmony_ci 1297db96d56Sopenharmony_ci Can be passed to the *stdin*, *stdout* or *stderr* parameters. 1307db96d56Sopenharmony_ci 1317db96d56Sopenharmony_ci If *PIPE* is passed to *stdin* argument, the 1327db96d56Sopenharmony_ci :attr:`Process.stdin <asyncio.subprocess.Process.stdin>` attribute 1337db96d56Sopenharmony_ci will point to a :class:`StreamWriter` instance. 1347db96d56Sopenharmony_ci 1357db96d56Sopenharmony_ci If *PIPE* is passed to *stdout* or *stderr* arguments, the 1367db96d56Sopenharmony_ci :attr:`Process.stdout <asyncio.subprocess.Process.stdout>` and 1377db96d56Sopenharmony_ci :attr:`Process.stderr <asyncio.subprocess.Process.stderr>` 1387db96d56Sopenharmony_ci attributes will point to :class:`StreamReader` instances. 1397db96d56Sopenharmony_ci 1407db96d56Sopenharmony_ci.. data:: asyncio.subprocess.STDOUT 1417db96d56Sopenharmony_ci :module: 1427db96d56Sopenharmony_ci 1437db96d56Sopenharmony_ci Special value that can be used as the *stderr* argument and indicates 1447db96d56Sopenharmony_ci that standard error should be redirected into standard output. 1457db96d56Sopenharmony_ci 1467db96d56Sopenharmony_ci.. data:: asyncio.subprocess.DEVNULL 1477db96d56Sopenharmony_ci :module: 1487db96d56Sopenharmony_ci 1497db96d56Sopenharmony_ci Special value that can be used as the *stdin*, *stdout* or *stderr* argument 1507db96d56Sopenharmony_ci to process creation functions. It indicates that the special file 1517db96d56Sopenharmony_ci :data:`os.devnull` will be used for the corresponding subprocess stream. 1527db96d56Sopenharmony_ci 1537db96d56Sopenharmony_ci 1547db96d56Sopenharmony_ciInteracting with Subprocesses 1557db96d56Sopenharmony_ci============================= 1567db96d56Sopenharmony_ci 1577db96d56Sopenharmony_ciBoth :func:`create_subprocess_exec` and :func:`create_subprocess_shell` 1587db96d56Sopenharmony_cifunctions return instances of the *Process* class. *Process* is a high-level 1597db96d56Sopenharmony_ciwrapper that allows communicating with subprocesses and watching for 1607db96d56Sopenharmony_citheir completion. 1617db96d56Sopenharmony_ci 1627db96d56Sopenharmony_ci.. class:: asyncio.subprocess.Process 1637db96d56Sopenharmony_ci :module: 1647db96d56Sopenharmony_ci 1657db96d56Sopenharmony_ci An object that wraps OS processes created by the 1667db96d56Sopenharmony_ci :func:`create_subprocess_exec` and :func:`create_subprocess_shell` 1677db96d56Sopenharmony_ci functions. 1687db96d56Sopenharmony_ci 1697db96d56Sopenharmony_ci This class is designed to have a similar API to the 1707db96d56Sopenharmony_ci :class:`subprocess.Popen` class, but there are some 1717db96d56Sopenharmony_ci notable differences: 1727db96d56Sopenharmony_ci 1737db96d56Sopenharmony_ci * unlike Popen, Process instances do not have an equivalent to 1747db96d56Sopenharmony_ci the :meth:`~subprocess.Popen.poll` method; 1757db96d56Sopenharmony_ci 1767db96d56Sopenharmony_ci * the :meth:`~asyncio.subprocess.Process.communicate` and 1777db96d56Sopenharmony_ci :meth:`~asyncio.subprocess.Process.wait` methods don't have a 1787db96d56Sopenharmony_ci *timeout* parameter: use the :func:`~asyncio.wait_for` function; 1797db96d56Sopenharmony_ci 1807db96d56Sopenharmony_ci * the :meth:`Process.wait() <asyncio.subprocess.Process.wait>` method 1817db96d56Sopenharmony_ci is asynchronous, whereas :meth:`subprocess.Popen.wait` method 1827db96d56Sopenharmony_ci is implemented as a blocking busy loop; 1837db96d56Sopenharmony_ci 1847db96d56Sopenharmony_ci * the *universal_newlines* parameter is not supported. 1857db96d56Sopenharmony_ci 1867db96d56Sopenharmony_ci This class is :ref:`not thread safe <asyncio-multithreading>`. 1877db96d56Sopenharmony_ci 1887db96d56Sopenharmony_ci See also the :ref:`Subprocess and Threads <asyncio-subprocess-threads>` 1897db96d56Sopenharmony_ci section. 1907db96d56Sopenharmony_ci 1917db96d56Sopenharmony_ci .. coroutinemethod:: wait() 1927db96d56Sopenharmony_ci 1937db96d56Sopenharmony_ci Wait for the child process to terminate. 1947db96d56Sopenharmony_ci 1957db96d56Sopenharmony_ci Set and return the :attr:`returncode` attribute. 1967db96d56Sopenharmony_ci 1977db96d56Sopenharmony_ci .. note:: 1987db96d56Sopenharmony_ci 1997db96d56Sopenharmony_ci This method can deadlock when using ``stdout=PIPE`` or 2007db96d56Sopenharmony_ci ``stderr=PIPE`` and the child process generates so much output 2017db96d56Sopenharmony_ci that it blocks waiting for the OS pipe buffer to accept 2027db96d56Sopenharmony_ci more data. Use the :meth:`communicate` method when using pipes 2037db96d56Sopenharmony_ci to avoid this condition. 2047db96d56Sopenharmony_ci 2057db96d56Sopenharmony_ci .. coroutinemethod:: communicate(input=None) 2067db96d56Sopenharmony_ci 2077db96d56Sopenharmony_ci Interact with process: 2087db96d56Sopenharmony_ci 2097db96d56Sopenharmony_ci 1. send data to *stdin* (if *input* is not ``None``); 2107db96d56Sopenharmony_ci 2. read data from *stdout* and *stderr*, until EOF is reached; 2117db96d56Sopenharmony_ci 3. wait for process to terminate. 2127db96d56Sopenharmony_ci 2137db96d56Sopenharmony_ci The optional *input* argument is the data (:class:`bytes` object) 2147db96d56Sopenharmony_ci that will be sent to the child process. 2157db96d56Sopenharmony_ci 2167db96d56Sopenharmony_ci Return a tuple ``(stdout_data, stderr_data)``. 2177db96d56Sopenharmony_ci 2187db96d56Sopenharmony_ci If either :exc:`BrokenPipeError` or :exc:`ConnectionResetError` 2197db96d56Sopenharmony_ci exception is raised when writing *input* into *stdin*, the 2207db96d56Sopenharmony_ci exception is ignored. This condition occurs when the process 2217db96d56Sopenharmony_ci exits before all data are written into *stdin*. 2227db96d56Sopenharmony_ci 2237db96d56Sopenharmony_ci If it is desired to send data to the process' *stdin*, 2247db96d56Sopenharmony_ci the process needs to be created with ``stdin=PIPE``. Similarly, 2257db96d56Sopenharmony_ci to get anything other than ``None`` in the result tuple, the 2267db96d56Sopenharmony_ci process has to be created with ``stdout=PIPE`` and/or 2277db96d56Sopenharmony_ci ``stderr=PIPE`` arguments. 2287db96d56Sopenharmony_ci 2297db96d56Sopenharmony_ci Note, that the data read is buffered in memory, so do not use 2307db96d56Sopenharmony_ci this method if the data size is large or unlimited. 2317db96d56Sopenharmony_ci 2327db96d56Sopenharmony_ci .. method:: send_signal(signal) 2337db96d56Sopenharmony_ci 2347db96d56Sopenharmony_ci Sends the signal *signal* to the child process. 2357db96d56Sopenharmony_ci 2367db96d56Sopenharmony_ci .. note:: 2377db96d56Sopenharmony_ci 2387db96d56Sopenharmony_ci On Windows, :py:data:`SIGTERM` is an alias for :meth:`terminate`. 2397db96d56Sopenharmony_ci ``CTRL_C_EVENT`` and ``CTRL_BREAK_EVENT`` can be sent to processes 2407db96d56Sopenharmony_ci started with a *creationflags* parameter which includes 2417db96d56Sopenharmony_ci ``CREATE_NEW_PROCESS_GROUP``. 2427db96d56Sopenharmony_ci 2437db96d56Sopenharmony_ci .. method:: terminate() 2447db96d56Sopenharmony_ci 2457db96d56Sopenharmony_ci Stop the child process. 2467db96d56Sopenharmony_ci 2477db96d56Sopenharmony_ci On POSIX systems this method sends :py:data:`signal.SIGTERM` to the 2487db96d56Sopenharmony_ci child process. 2497db96d56Sopenharmony_ci 2507db96d56Sopenharmony_ci On Windows the Win32 API function :c:func:`TerminateProcess` is 2517db96d56Sopenharmony_ci called to stop the child process. 2527db96d56Sopenharmony_ci 2537db96d56Sopenharmony_ci .. method:: kill() 2547db96d56Sopenharmony_ci 2557db96d56Sopenharmony_ci Kill the child process. 2567db96d56Sopenharmony_ci 2577db96d56Sopenharmony_ci On POSIX systems this method sends :py:data:`SIGKILL` to the child 2587db96d56Sopenharmony_ci process. 2597db96d56Sopenharmony_ci 2607db96d56Sopenharmony_ci On Windows this method is an alias for :meth:`terminate`. 2617db96d56Sopenharmony_ci 2627db96d56Sopenharmony_ci .. attribute:: stdin 2637db96d56Sopenharmony_ci 2647db96d56Sopenharmony_ci Standard input stream (:class:`StreamWriter`) or ``None`` 2657db96d56Sopenharmony_ci if the process was created with ``stdin=None``. 2667db96d56Sopenharmony_ci 2677db96d56Sopenharmony_ci .. attribute:: stdout 2687db96d56Sopenharmony_ci 2697db96d56Sopenharmony_ci Standard output stream (:class:`StreamReader`) or ``None`` 2707db96d56Sopenharmony_ci if the process was created with ``stdout=None``. 2717db96d56Sopenharmony_ci 2727db96d56Sopenharmony_ci .. attribute:: stderr 2737db96d56Sopenharmony_ci 2747db96d56Sopenharmony_ci Standard error stream (:class:`StreamReader`) or ``None`` 2757db96d56Sopenharmony_ci if the process was created with ``stderr=None``. 2767db96d56Sopenharmony_ci 2777db96d56Sopenharmony_ci .. warning:: 2787db96d56Sopenharmony_ci 2797db96d56Sopenharmony_ci Use the :meth:`communicate` method rather than 2807db96d56Sopenharmony_ci :attr:`process.stdin.write() <stdin>`, 2817db96d56Sopenharmony_ci :attr:`await process.stdout.read() <stdout>` or 2827db96d56Sopenharmony_ci :attr:`await process.stderr.read() <stderr>`. 2837db96d56Sopenharmony_ci This avoids deadlocks due to streams pausing reading or writing 2847db96d56Sopenharmony_ci and blocking the child process. 2857db96d56Sopenharmony_ci 2867db96d56Sopenharmony_ci .. attribute:: pid 2877db96d56Sopenharmony_ci 2887db96d56Sopenharmony_ci Process identification number (PID). 2897db96d56Sopenharmony_ci 2907db96d56Sopenharmony_ci Note that for processes created by the :func:`create_subprocess_shell` 2917db96d56Sopenharmony_ci function, this attribute is the PID of the spawned shell. 2927db96d56Sopenharmony_ci 2937db96d56Sopenharmony_ci .. attribute:: returncode 2947db96d56Sopenharmony_ci 2957db96d56Sopenharmony_ci Return code of the process when it exits. 2967db96d56Sopenharmony_ci 2977db96d56Sopenharmony_ci A ``None`` value indicates that the process has not terminated yet. 2987db96d56Sopenharmony_ci 2997db96d56Sopenharmony_ci A negative value ``-N`` indicates that the child was terminated 3007db96d56Sopenharmony_ci by signal ``N`` (POSIX only). 3017db96d56Sopenharmony_ci 3027db96d56Sopenharmony_ci 3037db96d56Sopenharmony_ci.. _asyncio-subprocess-threads: 3047db96d56Sopenharmony_ci 3057db96d56Sopenharmony_ciSubprocess and Threads 3067db96d56Sopenharmony_ci---------------------- 3077db96d56Sopenharmony_ci 3087db96d56Sopenharmony_ciStandard asyncio event loop supports running subprocesses from different threads by 3097db96d56Sopenharmony_cidefault. 3107db96d56Sopenharmony_ci 3117db96d56Sopenharmony_ciOn Windows subprocesses are provided by :class:`ProactorEventLoop` only (default), 3127db96d56Sopenharmony_ci:class:`SelectorEventLoop` has no subprocess support. 3137db96d56Sopenharmony_ci 3147db96d56Sopenharmony_ciOn UNIX *child watchers* are used for subprocess finish waiting, see 3157db96d56Sopenharmony_ci:ref:`asyncio-watchers` for more info. 3167db96d56Sopenharmony_ci 3177db96d56Sopenharmony_ci 3187db96d56Sopenharmony_ci.. versionchanged:: 3.8 3197db96d56Sopenharmony_ci 3207db96d56Sopenharmony_ci UNIX switched to use :class:`ThreadedChildWatcher` for spawning subprocesses from 3217db96d56Sopenharmony_ci different threads without any limitation. 3227db96d56Sopenharmony_ci 3237db96d56Sopenharmony_ci Spawning a subprocess with *inactive* current child watcher raises 3247db96d56Sopenharmony_ci :exc:`RuntimeError`. 3257db96d56Sopenharmony_ci 3267db96d56Sopenharmony_ciNote that alternative event loop implementations might have own limitations; 3277db96d56Sopenharmony_ciplease refer to their documentation. 3287db96d56Sopenharmony_ci 3297db96d56Sopenharmony_ci.. seealso:: 3307db96d56Sopenharmony_ci 3317db96d56Sopenharmony_ci The :ref:`Concurrency and multithreading in asyncio 3327db96d56Sopenharmony_ci <asyncio-multithreading>` section. 3337db96d56Sopenharmony_ci 3347db96d56Sopenharmony_ci 3357db96d56Sopenharmony_ciExamples 3367db96d56Sopenharmony_ci-------- 3377db96d56Sopenharmony_ci 3387db96d56Sopenharmony_ciAn example using the :class:`~asyncio.subprocess.Process` class to 3397db96d56Sopenharmony_cicontrol a subprocess and the :class:`StreamReader` class to read from 3407db96d56Sopenharmony_ciits standard output. 3417db96d56Sopenharmony_ci 3427db96d56Sopenharmony_ci.. _asyncio_example_create_subprocess_exec: 3437db96d56Sopenharmony_ci 3447db96d56Sopenharmony_ciThe subprocess is created by the :func:`create_subprocess_exec` 3457db96d56Sopenharmony_cifunction:: 3467db96d56Sopenharmony_ci 3477db96d56Sopenharmony_ci import asyncio 3487db96d56Sopenharmony_ci import sys 3497db96d56Sopenharmony_ci 3507db96d56Sopenharmony_ci async def get_date(): 3517db96d56Sopenharmony_ci code = 'import datetime; print(datetime.datetime.now())' 3527db96d56Sopenharmony_ci 3537db96d56Sopenharmony_ci # Create the subprocess; redirect the standard output 3547db96d56Sopenharmony_ci # into a pipe. 3557db96d56Sopenharmony_ci proc = await asyncio.create_subprocess_exec( 3567db96d56Sopenharmony_ci sys.executable, '-c', code, 3577db96d56Sopenharmony_ci stdout=asyncio.subprocess.PIPE) 3587db96d56Sopenharmony_ci 3597db96d56Sopenharmony_ci # Read one line of output. 3607db96d56Sopenharmony_ci data = await proc.stdout.readline() 3617db96d56Sopenharmony_ci line = data.decode('ascii').rstrip() 3627db96d56Sopenharmony_ci 3637db96d56Sopenharmony_ci # Wait for the subprocess exit. 3647db96d56Sopenharmony_ci await proc.wait() 3657db96d56Sopenharmony_ci return line 3667db96d56Sopenharmony_ci 3677db96d56Sopenharmony_ci date = asyncio.run(get_date()) 3687db96d56Sopenharmony_ci print(f"Current date: {date}") 3697db96d56Sopenharmony_ci 3707db96d56Sopenharmony_ci 3717db96d56Sopenharmony_ciSee also the :ref:`same example <asyncio_example_subprocess_proto>` 3727db96d56Sopenharmony_ciwritten using low-level APIs. 373