Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 3156 – 비동기 IO 지원 재시동: “asyncio” 모듈

Author:
Guido van Rossum <guido at python.org>
BDFL-Delegate:
Antoine Pitrou <antoine at python.org>
Discussions-To:
python-tulip@googlegroups.com
Status:
Final
Type:
Standards Track
Created:
12-Dec-2012
Python-Version:
3.3
Post-History:
21-Dec-2012
Replaces:
3153
Resolution:
Python-Dev message

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 문서는 Python 3.3부터 시작하여 Python 3에서 비동기 I/O를 지원하기 위한 제안입니다. 이 문서를 PEP 3153에 빠져 있던 구체적인 제안으로 생각하십시오. 이 제안에는 플러그인 방식으로 교체할 수 있는 이벤트 루프, Twisted의 것과 유사한 전송 및 프로토콜 추상화, 그리고 yield from(PEP 380)에 기반한 상위 수준의 스케줄러가 포함됩니다. 제안하는 패키지 이름은 asyncio입니다.

소개

상태

Tulip이라는 코드명으로 참조 구현이 존재합니다. Tulip 저장소 링크는 마지막의 참고 문헌 절에 있습니다. Python 3.3 설치 환경에서도 asyncio 패키지를 사용할 수 있도록 이 저장소에 기반한 패키지가 PyPI에서 제공될 예정입니다(참고 문헌 참조).

2013년 10월 20일 기준으로 asyncio 패키지는 Python 3.4 저장소에 체크인되었으며, “잠정적(provisional)” API 상태로 Python 3.4-alpha-4와 함께 릴리스되었습니다. 이는 신뢰의 표현이자 API에 대한 초기 피드백을 늘리기 위한 것이며, PEP의 수락을 강제하려는 의도는 아닙니다. 이 패키지는 Python 3.4에서 잠정적 상태를 유지하고 Python 3.5에서 최종 상태로 넘어갈 것으로 예상됩니다. 개발은 계속해서 주로 Tulip 저장소에서 이루어지며, 변경 사항은 때때로 CPython 저장소에 병합됩니다.

의존성

제안된 기능 중 상당수는 Python 3.3을 필요로 합니다. 참조 구현(Tulip)은 Windows에서의 (선택적인) IOCP 지원을 제외하면 Python 3.3 이후에 추가되는 새로운 언어 기능이나 표준 라이브러리 기능, 서드파티 모듈이나 패키지, C 코드를 전혀 필요로 하지 않습니다.

모듈 네임스페이스

여기서 다루는 명세는 새로운 최상위 패키지인 asyncio에 위치합니다. 서로 다른 구성 요소는 패키지의 별도 하위 모듈에 있습니다. 패키지는 공통 API를 각각의 하위 모듈에서 임포트하여 패키지 속성으로 제공합니다(email 패키지가 동작하는 방식과 유사합니다). 이러한 공통 API의 경우, 실제로 이를 정의하는 하위 모듈의 이름은 명세에 포함되지 않습니다. 덜 일반적인 API는 각각의 하위 모듈에서 명시적으로 임포트해야 할 수 있으며, 이 경우 하위 모듈 이름은 명세에 포함됩니다.

하위 모듈 이름 없이 정의된 클래스와 함수는 최상위 패키지의 네임스페이스에 있는 것으로 간주합니다. (다만 이를 여러 클래스의 메서드와 혼동하지 마십시오. 메서드 역시 간결함을 위해 특정 문맥에서는 네임스페이스 접두사 없이 사용됩니다.)

상호 운용성

대부분의 상호 운용은 이벤트 루프에서 이루어집니다. Twisted, Tornado, 심지어 gevents 같은 프레임워크(의 Python 3.3 포트)가 가벼운 어댑터나 프락시를 사용하여 기본 이벤트 루프 구현을 필요에 맞게 조정하거나, 기본 이벤트 루프 구현을 자체 이벤트 루프 구현을 조정한 것으로 교체하는 일이 쉬워야 합니다. (Twisted 같은 일부 프레임워크는 여러 개의 이벤트 루프 구현을 가지고 있습니다. 이들은 모두 같은 인터페이스를 가지므로 문제가 되지 않을 것입니다.)

대부분의 경우 서로 다른 두 서드파티 프레임워크는 기본 이벤트 루프 구현을 공유하거나(각자 자체 어댑터를 사용), 두 프레임워크 중 어느 한쪽의 이벤트 루프 구현을 공유하는 방식으로 상호 운용할 수 있어야 합니다. 후자의 경우 두 단계의 조정이 일어납니다(프레임워크 A의 이벤트 루프에서 표준 이벤트 루프 인터페이스로, 그리고 거기서 다시 프레임워크 B의 이벤트 루프로). 어떤 이벤트 루프 구현을 사용할지는 메인 프로그램이 제어해야 합니다(다만 이벤트 루프 선택을 위한 기본 정책이 제공됩니다).

이러한 상호 운용성이 효과를 거두려면, 서드파티 프레임워크에서 권장하는 적응 방향은 기본 이벤트 루프를 그대로 두고 이를 프레임워크의 API에 맞게 조정하는 것입니다. 이상적으로는 모든 서드파티 프레임워크가 자체 이벤트 루프 구현을 포기하고 표준 구현을 사용하는 것이 좋습니다. 그러나 모든 프레임워크가 표준 구현이 제공하는 기능에 만족하지는 않을 수 있습니다.

두 적응 방향을 모두 지원하기 위해 별도의 API 두 가지를 명세합니다:

  • 현재 이벤트 루프를 관리하기 위한 인터페이스
  • 규격을 준수하는 이벤트 루프의 인터페이스

이벤트 루프 구현은 추가 메서드와 보장을 제공할 수 있습니다. 단, 문서에서 이러한 사항이 비표준임을 명시해야 합니다. 이벤트 루프 구현은 주어진 환경에서 구현할 수 없는 특정 메서드를 구현하지 않은 채로 둘 수도 있습니다. 그러나 이처럼 표준 API에서 벗어나는 것은 최후의 수단으로만, 그리고 플랫폼이나 환경 때문에 어쩔 수 없는 경우에만 고려해야 합니다. (예를 들어 시작하거나 중지할 수 없는 시스템 이벤트 루프가 있는 플랫폼이 이에 해당합니다. 아래의 “임베디드 이벤트 루프”를 참조하십시오.)

이벤트 루프 API는 await/yield from에 의존하지 않습니다. 대신 콜백, 추가 인터페이스(전송과 프로토콜), 그리고 Future의 조합을 사용합니다. Future는 PEP 3148에서 정의한 것과 비슷하지만, 구현이 다르며 스레드에 묶여 있지 않습니다. 특히 result() 메서드는 결과가 아직 준비되지 않았을 때 블로킹하는 대신 예외를 발생시킵니다. 사용자는 콜백(또는 await/yield from)을 사용하여 결과를 기다려야 합니다.

코루틴을 반환하도록 명세된 모든 이벤트 루프 메서드는 구현의 선택에 따라 Future와 코루틴 중 어느 것이든 반환할 수 있습니다(표준 구현은 항상 코루틴을 반환합니다). 코루틴 인자를 받는다고 문서화된 모든 이벤트 루프 메서드는 해당 인자로 Future와 코루틴을 반드시 모두 받아들여야 합니다. (코루틴이나 Future인 인자를 Future로 변환하는 편의 함수 ensure_future()가 있습니다.)

(저처럼) 콜백 사용을 좋아하지 않는 사용자를 위해, PEP 380의 yield from 또는 PEP 492의 await 표현식을 사용하여 비동기 I/O 코드를 코루틴으로 작성할 수 있도록 스케줄러를 제공합니다. 스케줄러는 교체할 수 없습니다. 교체는 이벤트 루프 수준에서 이루어지며, 표준 스케줄러 구현은 규격을 준수하는 어떤 이벤트 루프 구현과도 동작해야 합니다. (사실 이는 규격을 준수하는 구현인지 가리는 중요한 리트머스 시험입니다.)

코루틴을 사용하여 작성한 코드와 다른 비동기 프레임워크 간의 상호 운용성을 위해, 스케줄러는 Future처럼 동작하는 Task 클래스를 정의합니다. 이벤트 루프 수준에서 상호 운용하는 프레임워크는 Future에 콜백을 추가하여 Future가 완료되기를 기다릴 수 있습니다. 마찬가지로 스케줄러는 콜백이 호출될 때까지 코루틴을 일시 중단하는 연산을 제공합니다.

그러한 프레임워크가 Future 및 Task 클래스를 있는 그대로 사용할 수 없다면, loop.create_future() 및 loop.create_task() 메서드를 다시 구현할 수 있습니다. 이 메서드들은 Future/Task 인터페이스(의 상위 집합)를 구현하는 객체를 반환해야 합니다.

덜 야심 찬 프레임워크라면 자체 이벤트 루프를 구현하지 않고 loop.set_task_factory()를 호출하여 Task 클래스만 교체할 수도 있습니다.

이벤트 루프 API는 스레드와 제한적으로 상호 운용할 수 있습니다. 함수를 실행기(PEP 3148 참조)에 제출하고 이벤트 루프와 호환되는 Future를 반환하는 API가 있으며, 다른 스레드에서 스레드 안전한 방식으로 이벤트 루프에 콜백을 예약하는 메서드도 있습니다.

전송과 프로토콜

Twisted에 익숙하지 않은 분들을 위해 전송과 프로토콜의 관계를 간단히 설명하겠습니다. 가장 높은 수준에서 보면, 트랜스포트는 바이트를 어떻게 전송할지를 다루고, 프로토콜은 어떤 바이트를 전송할지(그리고 어느 정도는 언제 전송할지)를 결정합니다.

같은 내용을 달리 표현하면, 전송의 관점에서 볼 때 전송은 소켓(또는 그와 유사한 I/O 엔드포인트)에 대한 추상화이고, 프로토콜은 애플리케이션에 대한 추상화입니다.

또 다른 관점은 단순히 전송 인터페이스와 프로토콜 인터페이스가 함께 네트워크 I/O와 프로세스 간 I/O를 사용하기 위한 추상 인터페이스를 정의한다는 것입니다.

전송 객체와 프로토콜 객체 사이에는 거의 항상 1:1 관계가 있습니다. 프로토콜은 데이터를 보내기 위해 전송의 메서드를 호출하고, 전송은 수신한 데이터를 넘겨주기 위해 프로토콜의 메서드를 호출합니다. 전송의 메서드도 프로토콜의 메서드도 “블로킹”하지 않습니다. 이들은 이벤트를 발생시킨 뒤 바로 반환합니다.

가장 일반적인 전송 유형은 양방향 스트림 전송입니다. 이는 버퍼링된 스트림 한 쌍(방향마다 하나씩)을 나타내며, 각 스트림은 바이트 시퀀스를 전송합니다. 양방향 스트림 전송의 가장 일반적인 예는 아마도 TCP 연결일 것입니다. 또 다른 흔한 예로는 SSL/TLS 연결이 있습니다. 하지만 이런 식으로 볼 수 있는 다른 것들도 있습니다. 예를 들어 SSH 세션이나 UNIX 파이프 한 쌍이 그렇습니다. 일반적으로 전송 구현의 종류는 많지 않으며, 대부분은 이벤트 루프 구현과 함께 제공됩니다. 그러나 모든 전송이 반드시 이벤트 루프 메서드를 호출하여 생성되어야 한다는 요구 사항은 없습니다. 서드파티 모듈이 새로운 전송을 구현하고, 단순히 이벤트 루프를 인자로 받거나 get_event_loop()를 호출하는 생성자나 팩토리 함수를 제공해도 무방합니다.

전송은 소켓을 사용할 필요가 없으며, TCP를 사용하더라도 마찬가지라는 점에 유의하십시오. 소켓은 플랫폼별 구현 세부 사항입니다.

양방향 스트림 전송에는 두 개의 “끝”이 있습니다. 한쪽 끝은 네트워크(또는 다른 프로세스, 혹은 전송이 감싸는 어떤 저수준 인터페이스든)와 통신하고, 다른 쪽 끝은 프로토콜과 통신합니다. 전자는 전송을 구현하는 데 필요한 어떤 API든 사용하지만, 전송과 프로토콜 사이의 인터페이스는 이 PEP에서 표준화합니다.

프로토콜은 HTTP나 SMTP 같은 일종의 “애플리케이션 수준” 프로토콜을 나타낼 수 있습니다. 또한 여러 프로토콜이 공유하는 추상화나 애플리케이션 전체를 구현할 수도 있습니다. 프로토콜의 주된 인터페이스는 전송과의 인터페이스입니다. 일부 널리 쓰이는 프로토콜(및 기타 추상화)에는 표준 구현이 있을 수 있지만, 애플리케이션이 사용자 정의 프로토콜을 구현하는 경우가 많습니다. PyPI에서 내려받아 설치할 수 있는 유용한 서드파티 프로토콜 구현 라이브러리를 두는 것도 합리적입니다.

전송과 프로토콜이라는 일반적인 개념에는 전송이 다른 통신 추상화를 감싸는 경우의 다른 인터페이스도 포함됩니다. 예로는 데이터그램(예: UDP)을 송수신하는 인터페이스나 서브프로세스 관리자가 있습니다. 관심사의 분리는 양방향 스트림 전송 및 프로토콜과 동일하지만, 전송과 프로토콜 사이의 구체적인 인터페이스는 경우마다 다릅니다.

다양한 표준 유형의 전송과 프로토콜이 정의하는 인터페이스의 세부 사항은 뒤에서 설명합니다.

이벤트 루프 인터페이스 명세

이벤트 루프 정책: 현재 이벤트 루프 가져오기와 설정하기

이벤트 루프 관리는 전역(프로세스별) 객체인 이벤트 루프 정책이 제어합니다. 기본 정책이 있으며, 정책을 변경하는 API가 있습니다. 정책은 컨텍스트의 개념을 정의하며, 컨텍스트마다 별도의 이벤트 루프를 관리합니다. 기본 정책에서 컨텍스트의 개념은 현재 스레드로 정의됩니다.

특정 플랫폼이나 프로그래밍 프레임워크는 기본 정책을 해당 플랫폼이나 프레임워크 사용자의 기대에 더 적합한 것으로 변경할 수 있습니다. 이러한 플랫폼이나 프레임워크는 여러 활성 프레임워크가 기본 정책을 재정의하려 할 때 정의되지 않은 동작을 피하기 위해, 자신의 정책과 초기화 과정의 어느 시점에 정책을 설정하는지를 문서화해야 합니다. (아래의 “임베디드 이벤트 루프”도 참조하십시오.)

현재 컨텍스트의 이벤트 루프를 가져오려면 get_event_loop()를 사용하십시오. 이 함수는 아래에 명시된 인터페이스를 구현하는 이벤트 루프 객체를 반환하거나, 현재 컨텍스트에 설정된 이벤트 루프가 없고 현재 정책이 이벤트 루프를 생성하도록 지정하지 않은 경우 예외를 발생시킵니다. 이 함수는 절대 None을 반환해서는 안 됩니다.

현재 컨텍스트의 이벤트 루프를 설정하려면 set_event_loop(event_loop)를 사용하십시오. 여기서 event_loop는 이벤트 루프 객체, 즉 AbstractEventLoop의 인스턴스이거나 None입니다. 현재 이벤트 루프를 None으로 설정해도 괜찮으며, 이 경우 이후에 get_event_loop()를 호출하면 예외가 발생합니다. 이는 기본 이벤트 루프의 존재에 의존해서는 안 되는 코드를 테스트할 때 유용합니다.

get_event_loop()는 컨텍스트에 따라 다른 이벤트 루프 객체를 반환할 것으로 기대됩니다(사실 이것이 바로 컨텍스트의 정의입니다). 설정된 이벤트 루프가 없고 정책이 생성을 허용하면 새 이벤트 루프 객체를 만들 수도 있습니다. 기본 정책은 메인 스레드(메인 스레드에 특수 서브클래스를 사용하는 threading.py가 정의하는 메인 스레드)에서만, 그리고 set_event_loop()가 한 번이라도 호출되기 전에 get_event_loop()가 호출된 경우에만 새 이벤트 루프를 만듭니다. (이 상태를 초기화하려면 정책을 초기화하십시오.) 다른 스레드에서는 이벤트 루프를 명시적으로 설정해야 합니다. 다른 정책은 다르게 동작할 수 있습니다. 기본 정책에 의한 이벤트 루프 생성은 지연(lazy) 방식입니다. 즉, get_event_loop()를 처음 호출할 때, 필요하고 현재 정책이 그렇게 지정한 경우에 이벤트 루프 인스턴스가 만들어집니다.

단위 테스트와 기타 특수한 경우를 위해 세 번째 정책 함수인 new_event_loop()가 있습니다. 이 함수는 정책의 기본 규칙에 따라 새 이벤트 루프 객체를 만들어 반환합니다. 이를 현재 이벤트 루프로 만들려면 이 객체를 인자로 하여 set_event_loop()를 호출해야 합니다.

이벤트 루프 정책을 변경하려면 set_event_loop_policy(policy)를 호출하십시오. 여기서 policy는 이벤트 루프 정책 객체 또는 None입니다. None이 아니라면, 정책 객체는 get_event_loop(), set_event_loop(loop), new_event_loop() 메서드를 정의하는 AbstractEventLoopPolicy의 인스턴스여야 하며, 이 메서드들은 모두 위에서 설명한 함수들처럼 동작해야 합니다.

정책 값으로 None을 전달하면 기본 이벤트 루프 정책이 복원됩니다(플랫폼이나 프레임워크가 설정한 대체 기본값을 덮어씁니다). 기본 이벤트 루프 정책은 DefaultEventLoopPolicy 클래스의 인스턴스입니다. 현재 이벤트 루프 정책 객체는 get_event_loop_policy()를 호출하여 가져올 수 있습니다.

미정: 서브프로세스 처리를 위한 자식 감시자(child watcher)와 UNIX 특유의 동작을 설명할 예정입니다.

이벤트 루프를 명시적으로 전달하기

전역 또는 스레드별 기본 이벤트 루프에 의존하지 않고 이벤트 루프를 사용하는 코드를 작성할 수 있습니다. 이를 위해 현재 이벤트 루프에 접근해야 하는 (그리고 이벤트 클래스의 메서드가 아닌) 모든 API는 loop라는 이름의 선택적 키워드 인자를 받습니다. 이 인자가 None이거나 지정되지 않으면 이러한 API는 get_event_loop()를 호출하여 기본 이벤트 루프를 가져오지만, loop 키워드 인자가 이벤트 루프 객체로 설정되어 있으면 그 이벤트 루프를 사용하고, 자신이 호출하는 다른 같은 종류의 API에도 그것을 전달합니다. 예를 들어 Future(loop=my_loop)는 이벤트 루프 my_loop에 묶인 Future를 만듭니다. 기본 현재 이벤트가 None이면 loop 키워드 인자는 사실상 필수입니다.

명시적으로 전달된 이벤트 루프도 여전히 현재 스레드에 속해야 한다는 점에 유의하십시오. loop 키워드 인자가 이벤트 루프 사용 방식에 대한 제약을 마법처럼 바꾸어 주지는 않습니다.

시간 지정하기

Python에서 늘 그렇듯이, 모든 타임아웃, 간격, 지연은 초 단위로 측정하며 int 또는 float일 수 있습니다. 하지만 절대 시간은 POSIX 타임스탬프로 지정되지 않습니다. 시계의 정확도, 정밀도, 기준 시점(epoch)은 구현에 달려 있습니다.

기본 구현은 time.monotonic()을 사용합니다. 이 선택이 갖는 의미에 대해서는 책 몇 권을 쓸 수도 있을 것입니다. 표준 라이브러리 time 모듈의 문서를 읽어 보는 편이 좋습니다.

임베디드 이벤트 루프

일부 플랫폼에서는 시스템이 이벤트 루프를 제공합니다. 이러한 루프는 사용자 코드가 시작될 때 이미 실행 중일 수 있으며, 프로그램을 종료하지 않고는 루프를 멈추거나 닫을 방법이 없을 수도 있습니다. 이 경우 이벤트 루프를 시작, 중지, 종료하는 메서드는 구현이 불가능할 수 있으며, is_running()은 항상 True를 반환할 수도 있습니다.

이벤트 루프 클래스

EventLoop이라는 이름의 실제 클래스는 없습니다. 모든 메서드를 구현 없이 정의하며 주로 문서 역할을 하는 AbstractEventLoop 클래스가 있습니다. 다음과 같은 구체 클래스가 정의되어 있습니다:

  • SelectorEventLoop는 selectors 모듈(Python 3.4에서 새로 추가됨)을 기반으로 전체 API를 구현한 구체 구현입니다. 생성자는 선택적 인자 하나, 즉 selectors.Selector 객체를 받습니다. 기본적으로 selectors.DefaultSelector의 인스턴스가 생성되어 사용됩니다.
  • ProactorEventLoop는 I/O 이벤트 처리 메서드와 시그널 처리 메서드를 제외한 API의 구체 구현입니다. 이 클래스는 Windows에서만(또는 “overlapped I/O”를 위한 유사한 API를 지원하는 다른 플랫폼에서만) 정의됩니다. 생성자는 선택적 인자 하나, 즉 Proactor 객체를 받습니다. 기본적으로 IocpProactor의 인스턴스가 생성되어 사용됩니다. (IocpProactor 클래스는 이 PEP에서 명세하지 않으며, ProactorEventLoop 클래스의 구현 세부 사항일 뿐입니다.)

이벤트 루프 메서드 개요

규격을 준수하는 이벤트 루프의 메서드는 여러 범주로 묶입니다. 첫 번째 범주 집합은 모든 호환 이벤트 루프 구현이 지원해야 합니다. 단, 예외적으로 임베디드 이벤트 루프는 시작, 중지, 닫기 메서드를 구현하지 않아도 됩니다. (그래도 부분적으로나마 호환되는 이벤트 루프가 없는 것보다는 낫습니다. :-)

  • 시작, 중지, 닫기: run_forever(), run_until_complete(), stop(), is_running(), close(), is_closed().
  • 기본 콜백과 시간 지정 콜백: call_soon(), call_later(), call_at(), time().
  • 스레드 상호 작용: call_soon_threadsafe(), run_in_executor(), set_default_executor().
  • 인터넷 이름 조회: getaddrinfo(), getnameinfo().
  • 인터넷 연결: create_connection(), create_server(), create_datagram_endpoint().
  • 래핑된 소켓 메서드: sock_recv(), sock_sendall(), sock_connect(), sock_accept().
  • 태스크와 퓨처 지원: create_future(), create_task(), set_task_factory(), get_task_factory().
  • 오류 처리: get_exception_handler(), set_exception_handler(), default_exception_handler(), call_exception_handler().
  • 디버그 모드: get_debug(), set_debug().

두 번째 범주 집합은 규격을 준수하는 이벤트 루프 구현이 지원할 수도 있습니다. 지원하지 않는 경우에는 NotImplementedError를 발생시킵니다. (기본 구현에서 UNIX 시스템의 SelectorEventLoop는 이들을 모두 지원하고, Windows의 SelectorEventLoop는 I/O 이벤트 처리 범주를 지원하며, Windows의 ProactorEventLoop는 파이프와 서브프로세스 범주를 지원합니다.)

  • I/O 콜백: add_reader(), remove_reader(), add_writer(), remove_writer().
  • 파이프와 서브프로세스: connect_read_pipe(), connect_write_pipe(), subprocess_shell(), subprocess_exec().
  • 시그널 콜백: add_signal_handler(), remove_signal_handler().

이벤트 루프 메서드

시작, 중지, 닫기

(닫히지 않은) 이벤트 루프는 실행 중 또는 중지됨의 두 가지 상태 중 하나에 있을 수 있습니다. 다음 메서드는 이벤트 루프의 시작과 중지를 다룹니다:

  • run_forever(). stop()이 호출될 때까지 이벤트 루프를 실행합니다. 이벤트 루프가 이미 실행 중일 때는 이 메서드를 호출할 수 없습니다. (이 메서드의 이름이 긴 데에는 여러 이유가 있습니다. run()이 다르게 동작했던 이 PEP의 이전 버전과 혼동되지 않게 하려는 이유도 있고, run()이라는 이름의 메서드를 가진 API가 이미 너무 많다는 이유도 있으며, 어차피 이 메서드를 호출하는 곳이 많지 않을 것이라는 이유도 있습니다.)
  • run_until_complete(future). Future가 완료될 때까지 이벤트 루프를 실행합니다. Future가 완료되면 그 결과를 반환하거나 그 예외를 발생시킵니다. 이벤트 루프가 이미 실행 중일 때는 이 메서드를 호출할 수 없습니다. 매개변수가 코루틴이면 이 메서드는 새 Task 객체를 생성합니다.
  • stop(). 적절한 시점이 되는 대로 이벤트 루프를 중지합니다. 이후에 run_forever() 또는 run_until_complete()로 루프를 다시 시작해도 괜찮습니다. 그렇게 해도 예약된 콜백은 하나도 손실되지 않습니다. 참고: stop()은 정상적으로 반환되며 현재 콜백은 계속 실행될 수 있습니다. 이 시점 이후 이벤트 루프가 얼마나 빨리 중지되는지는 구현에 달려 있지만, I/O 폴링 전에 멈추고 앞으로 예약된 콜백은 실행하지 않는 것이 의도입니다. 구현에 주어진 주된 재량은 중지하기 전에 “준비 큐”(call_soon()으로 이미 예약된 콜백)를 얼마나 처리하는가입니다.
  • is_running(). 이벤트 루프가 현재 실행 중이면 True를, 중지되어 있으면 False를 반환합니다.
  • close(). 이벤트 루프를 닫고, epoll()이나 kqueue()가 사용하는 파일 디스크립터와 기본 실행기(executor) 등 이벤트 루프가 보유하고 있을 수 있는 자원을 해제합니다. 이벤트 루프가 실행 중인 동안에는 이 메서드를 호출해서는 안 됩니다. 이 메서드가 호출된 후에는 이벤트 루프를 다시 사용해서는 안 됩니다. 이 메서드는 여러 번 호출할 수 있으며, 두 번째 이후의 호출은 아무 동작도 하지 않습니다.
  • is_closed(). 이벤트 루프가 닫혀 있으면 True를, 그렇지 않으면 False를 반환합니다. (주로 오류 보고를 위한 것이므로 이 메서드에 기반하여 기능을 구현하지 마십시오.)

기본 콜백

같은 이벤트 루프에 연결된 콜백들은 엄격하게 직렬화됩니다. 즉, 한 콜백이 끝나야 다음 콜백이 호출됩니다. 이는 중요한 보장입니다. 둘 이상의 콜백이 공유 상태를 사용하거나 수정할 때, 각 콜백은 자신이 실행되는 동안 다른 콜백이 공유 상태를 변경하지 않는다는 것을 보장받습니다.

  • call_soon(callback, *args). 가능한 한 빨리 호출되도록 콜백을 예약합니다. 콜백을 나타내는 Handle을 반환하며(아래 참조), 이 객체의 cancel() 메서드를 사용하여 콜백을 취소할 수 있습니다. 콜백이 예약된 순서대로 호출되도록 보장합니다.
  • call_later(delay, callback, *args). 취소되지 않는 한, 약 delay 초 후에 callback(*args)가 한 번 호출되도록 예약합니다. 콜백을 나타내는 Handle을 반환합니다. 이 객체의 cancel() 메서드로 콜백을 취소할 수 있습니다. 과거 시점이나 정확히 같은 시각에 예약된 콜백은 정해지지 않은 순서로 호출됩니다.
  • call_at(when, callback, *args). call_later()와 비슷하지만, 시간을 절대 시각으로 나타냅니다. 비슷한 Handle을 반환합니다. 간단한 동치 관계가 있습니다. loop.call_later(delay, callback, *args)는 loop.call_at(loop.time() + delay, callback, *args)와 같습니다.
  • time(). 이벤트 루프의 시계에 따른 현재 시각을 반환합니다. 이는 time.time()이나 time.monotonic(), 또는 다른 시스템별 시계일 수 있습니다. 하지만 어떤 기준 시점(epoch) 이후의 시간을 대략 1초 단위로 나타내는 float를 반환해야 합니다. (완벽한 시계는 없습니다. PEP 418을 참고하십시오.)

참고: 이 PEP의 이전 버전에는 call_repeatedly()라는 메서드가 정의되어 있었습니다. 이 메서드는 일정한 간격으로 콜백을 호출해 주기로 되어 있었습니다. 그런 함수를 설계하려면 지나치게 많은 것을 규정해야 하므로 이 메서드는 철회되었습니다. 한편으로, 간단한 타이머 루프는 call_later()로 스스로를 다시 예약하는 콜백을 사용해 쉽게 흉내 낼 수 있습니다. 루프와 sleep() 호출(모듈의 최상위 함수로, 아래를 참고하십시오)을 포함하는 코루틴을 작성하기도 쉽습니다. 다른 한편으로, 정확한 시간 관리는 복잡하므로 이를 잘 모르는 사람이 빠지기 쉬운 함정이 많습니다(PEP 418 참고). 또한 사용 사례마다 경계 상황에서 요구하는 동작이 다릅니다. 이 목적으로 모든 경우에 완벽하게 안전한 API를 제공하는 것은 불가능합니다. 그래서 어떤 종류의 타이머 루프를 구현할지는 애플리케이션 설계자가 스스로 결정하도록 하는 편이 낫다고 판단했습니다.

스레드 상호 작용

  • call_soon_threadsafe(callback, *args). call_soon(callback, *args)와 비슷합니다. 다만 이벤트 루프가 I/O를 기다리며 블록된 동안 다른 스레드에서 호출되면, 이벤트 루프의 블록을 해제합니다. Handle을 반환합니다. 이것은 다른 스레드에서 호출해도 안전한 유일한 메서드입니다. (나중 시각의 콜백을 스레드 안전하게 예약하려면 loop.call_soon_threadsafe(loop.call_later, when, callback, *args)를 사용할 수 있습니다.) 참고: 이 메서드는 잠금을 사용할 수 있으므로 시그널 핸들러에서 호출하는 것은 안전하지 않습니다. 사실 시그널에 안전한 API는 없습니다. 시그널을 처리하려면 아래에서 설명하는 add_signal_handler()를 사용하십시오.
  • run_in_executor(executor, callback, *args). 실행기(executor)에서 callback(*args)를 호출하도록 예약합니다(PEP 3148 참고). asyncio.Future 인스턴스를 반환합니다. 호출이 성공하면 이 인스턴스의 결과는 그 호출의 반환값입니다. 이는 wrap_future(executor.submit(callback, *args))와 동등합니다. executor가 None이면 set_default_executor()로 설정한 기본 실행기를 사용합니다. 아직 기본 실행기가 설정되지 않았다면, 기본 개수의 스레드를 갖는 ThreadPoolExecutor를 생성하여 기본 실행기로 설정합니다. (이 경우 기본 구현은 스레드 5개를 사용합니다.)
  • set_default_executor(executor). run_in_executor()가 사용하는 기본 실행기를 설정합니다. 인자는 PEP 3148 Executor 인스턴스여야 하며, 기본 실행기를 재설정하려면 None을 전달합니다.

Future에 관한 절에서 설명하는 wrap_future() 함수도 참고하십시오.

인터넷 이름 조회

이 메서드들은 이름 조회 때문에 블로킹될 위험 없이 소켓을 주소에 연결하거나 바인딩하려는 경우에 유용합니다. 이 메서드들은 보통 create_connection(), create_server() 또는 create_datagram_endpoint()에 의해 암묵적으로 호출됩니다.

  • getaddrinfo(host, port, family=0, type=0, proto=0, flags=0). socket.getaddrinfo() 함수와 비슷하지만 Future를 반환합니다. 성공 시 Future의 결과는 socket.getaddrinfo()가 반환하는 것과 같은 형식의 리스트입니다. 즉, (address_family, socket_type, socket_protocol, canonical_name, address)의 리스트이며, 여기서 address는 IPv4 주소의 경우 2-튜플 (ipv4_address, port)이고 IPv6 주소의 경우 4-튜플 (ipv6_address, port, flow_info, scope_id)입니다. family 인자가 0이거나 지정되지 않으면 반환되는 리스트에 IPv4 주소와 IPv6 주소가 섞여 있을 수 있습니다. 그렇지 않으면 반환되는 주소는 family 값에 따라 제한됩니다(proto와 flags도 마찬가지입니다). 기본 구현은 run_in_executor()를 사용하여 socket.getaddrinfo()를 호출하지만, 다른 구현은 자체 DNS 조회를 구현할 수도 있습니다. 선택적 인자는 반드시 키워드 인자로 지정해야 합니다.

    참고: 구현은 전체 socket.getaddrinfo() 인터페이스의 일부만 구현해도 됩니다. 예를 들어 기호로 된 포트 이름을 지원하지 않거나, type, proto, flags 인자를 무시하거나 불완전하게 구현할 수 있습니다. 그러나 type과 proto를 무시하는 경우에는 전달된 인자 값을 반환 튜플의 socket_type 및 socket_protocol 요소에 변경 없이 복사해야 합니다. (IPv4 주소와 IPv6 주소는 서로 다르게 조회해야 하므로 family는 무시할 수 없습니다. family에 허용되는 값은 socket.AF_UNSPEC (0), socket.AF_INET, socket.AF_INET6뿐이며, 마지막 값은 플랫폼에 정의되어 있는 경우에만 허용됩니다.)

  • getnameinfo(sockaddr, flags=0). socket.getnameinfo()와 비슷하지만 Future를 반환합니다. 성공 시 Future의 결과는 튜플 (host, port)입니다. 구현에 관한 참고 사항은 getaddrinfo()와 같습니다.

인터넷 연결

다음은 인터넷 연결을 관리하기 위한 고수준 인터페이스입니다. 이 인터페이스들은 셀렉터 기반 이벤트 루프와 프로액터 기반 이벤트 루프의 차이를 추상화하므로, 대응하는 저수준 인터페이스보다 이 인터페이스들을 사용하는 것을 권장합니다.

스트림 연결의 클라이언트 측과 서버 측은 동일한 전송 및 프로토콜 인터페이스를 사용한다는 점에 유의하십시오. 그러나 데이터그램 엔드포인트는 다른 전송 및 프로토콜 인터페이스를 사용합니다.

  • create_connection(protocol_factory, host, port, <options>). 지정한 인터넷 호스트와 포트에 대한 스트림 연결을 생성합니다. 이것은 일반적으로 연결의 클라이언트 측에서 호출하는 작업입니다. 이 메서드는 연결을 나타내는 구현 의존적인 양방향 스트림 전송(Transport)을 생성한 다음, protocol_factory()를 호출하여 사용자의 프로토콜(Protocol) 구현을 인스턴스화(또는 조회)하고, 마지막으로 둘을 서로 연결합니다. (전송(Transport)과 프로토콜(Protocol)의 정의는 아래를 참조하십시오.) 사용자의 프로토콜(Protocol) 구현은 protocol_factory()를 인자 없이 호출하여 생성하거나 조회합니다(*). 성공 시 코루틴의 결과는 (transport, protocol) 쌍입니다. 실패로 인해 연결을 성공적으로 생성할 수 없으면 적절한 예외가 발생합니다. 코루틴이 완료된 시점에는 프로토콜의 connection_made() 메서드가 아직 호출되지 않았다는 점에 유의하십시오. 이 메서드는 연결 핸드셰이크가 완료될 때 호출됩니다.

    (*) protocol_factory가 반드시 클래스일 필요는 없습니다. 프로토콜 클래스의 생성자에 특정 인자를 전달해야 한다면 lambda를 사용할 수 있습니다. 이전에 생성해 둔 프로토콜(Protocol) 인스턴스를 반환하는 간단한 lambda를 전달할 수도 있습니다.

    <options>는 모두 선택적 키워드 인자로 지정합니다:

    • ssl: SSL/TLS 전송을 생성하려면 True를 전달하십시오(기본적으로는 일반 TCP 전송이 생성됩니다). 또는 사용할 기본 SSL 컨텍스트 객체를 재정의하려면 ssl.SSLContext 객체를 전달하십시오. 기본 컨텍스트가 생성되는 경우, 합리적인 기본값을 구성하는 것은 구현의 몫입니다. 참조 구현은 현재 PROTOCOL_SSLv23을 사용하고, OP_NO_SSLv2 옵션을 설정하며, set_default_verify_paths()를 호출하고, verify_mode를 CERT_REQUIRED로 설정합니다. 또한 컨텍스트(기본 컨텍스트이든 아니든)가 verify_mode를 CERT_REQUIRED 또는 CERT_OPTIONAL로 지정할 때는 언제나, 호스트 이름이 주어졌다면 핸드셰이크가 성공한 직후 ssl.match_hostname(peercert, hostname)이 호출되며, 여기서 예외가 발생하면 연결이 닫힙니다. (이 동작을 피하려면 verify_mode가 CERT_NONE으로 설정된 SSL 컨텍스트를 전달하십시오. 하지만 이는 안전하지 않다는 뜻이며, 예를 들어 중간자 공격에 취약해집니다.)
    • family, proto, flags: getaddrinfo()에 그대로 전달될 주소 패밀리, 프로토콜, 플래그입니다. 이들의 기본값은 모두 0이며, 이는 “지정되지 않음”을 의미합니다. (소켓 유형은 항상 SOCK_STREAM입니다.) 이 값들 중 지정되지 않은 것이 있으면 getaddrinfo() 메서드가 적절한 값을 선택합니다. 참고: proto는 고수준 프로토콜 개념이나 protocol_factory 인자와 아무 관련이 없습니다.
    • sock: host, port, family, proto, flags 인자를 사용하는 대신 사용할 선택적 소켓입니다. 이 값이 주어지면 host와 port를 명시적으로 None으로 설정해야 합니다.
    • local_addr: 주어진 경우, 소켓을 로컬에 바인딩하는 데 사용되는 (host, port) 튜플입니다. 이는 거의 필요하지 않지만, 멀티홈 서버에서는 가끔 연결이 특정 주소에서 시작되도록 강제해야 할 때가 있습니다. 그럴 때 이 인자를 사용합니다. 호스트와 포트는 getaddrinfo()를 사용하여 조회됩니다.
    • server_hostname: 이는 SSL/TLS를 사용할 때만 의미가 있으며, ssl이 설정되지 않았을 때는 사용해서는 안 됩니다. ssl이 설정된 경우, 이는 검증할 호스트 이름을 설정하거나 재정의합니다. 기본적으로는 host 인자의 값이 사용됩니다. host가 비어 있으면 기본값이 없으므로 server_hostname에 값을 전달해야 합니다. 호스트 이름 검증을 비활성화하려면(이는 심각한 보안 위험입니다) 여기에 빈 문자열을 전달하고, verify_mode가 ssl.CERT_NONE으로 설정된 ssl.SSLContext 객체를 ssl 인자로 전달해야 합니다.
  • create_server(protocol_factory, host, port, <options>). 연결을 수락하는 서비스 루프에 진입합니다. 이는 서비스 루프가 서비스할 준비를 마치면 완료되는 코루틴입니다. 반환값은 서비스 루프를 통제된 방식으로 중지하는 데 사용할 수 있는 Server 객체입니다(아래 참조). 지정된 주소가 IPv4와 IPv6 연결을 모두 허용하는 경우 여러 소켓이 바인딩될 수 있습니다.

    연결이 수락될 때마다 protocol_factory가 인자 없이 호출되어(**) 프로토콜(Protocol)을 생성하고, 연결의 네트워크 측을 나타내는 양방향 스트림 전송(Transport)이 생성되며, protocol.connection_made(transport)를 호출하여 둘을 서로 연결합니다.

    (**) create_connection()에 대한 앞의 각주를 참조하십시오. 하지만 protocol_factory()는 새로 들어오는 연결마다 한 번씩 호출되므로, 호출될 때마다 새로운 프로토콜 객체를 반환해야 합니다.

    <options>는 모두 선택적 키워드 인자를 사용하여 지정합니다:

    • ssl: 사용할 기본 SSL 컨텍스트 객체를 재정의하려면 ssl.SSLContext 객체(또는 같은 인터페이스를 가진 객체)를 전달하십시오. (create_connection()과 달리 여기서는 True를 전달하는 것이 의미가 없습니다 – 인증서와 키를 지정하려면 SSLContext 객체가 필요하기 때문입니다.)
    • backlog: listen() 호출에 전달될 백로그 값입니다. 기본값은 구현에 따라 다르며, 기본 구현에서의 기본값은 100입니다.
    • reuse_address: 소켓에 SO_REUSEADDR 옵션을 설정할지 여부입니다. 기본값은 UNIX에서는 True, Windows에서는 False입니다.
    • family, flags: getaddrinfo()에 그대로 전달될 주소 패밀리와 플래그입니다. 패밀리의 기본값은 AF_UNSPEC이고, 플래그의 기본값은 AI_PASSIVE입니다. (소켓 타입은 항상 SOCK_STREAM이며, getaddrinfo()가 선택할 수 있도록 소켓 프로토콜은 항상 0으로 설정됩니다.)
    • sock: host, port, family, flags 인자를 사용하는 대신 사용할 선택적 소켓입니다. 이 인자를 지정하면 host와 port를 명시적으로 None으로 설정해야 합니다.
  • create_datagram_endpoint(protocol_factory, local_addr=None, remote_addr=None, <options>). 데이터그램(일반적으로 UDP 패킷)을 송수신하기 위한 엔드포인트를 생성합니다. 데이터그램 트래픽의 특성상 보통 하나의 엔드포인트가 클라이언트와 서버 역할을 모두 하므로, 클라이언트 측과 서버 측을 설정하는 호출이 따로 있지 않습니다. 이것은 성공하면 (transport, protocol) 쌍을 반환하고, 실패하면 예외를 발생시키는 코루틴입니다. 코루틴이 성공적으로 반환되면, 전송은 데이터그램이 수신되거나 소켓이 닫힐 때마다 프로토콜의 콜백을 호출합니다; 데이터그램을 보내기 위해 프로토콜에서 메서드를 호출하는 것은 프로토콜의 몫입니다. 반환되는 전송은 DatagramTransport입니다. 반환되는 프로토콜은 DatagramProtocol입니다. 이들은 뒤에서 설명합니다.

    필수 위치 인자:

    • protocol_factory: 반환할 프로토콜 객체를 생성하기 위해 인자 없이 정확히 한 번 호출될 클래스 또는 팩토리 함수입니다. 데이터그램 전송과 프로토콜 사이의 인터페이스는 아래에서 설명합니다.

    위치 인자나 키워드 인자로 지정할 수 있는 선택적 인자:

    • local_addr: 소켓을 바인딩할 주소를 나타내는 선택적 튜플입니다. 지정하는 경우 반드시 (host, port) 쌍이어야 합니다. 이 값은 getaddrinfo()에 전달되어 해석되고, 그 결과는 생성된 소켓의 bind() 메서드에 전달됩니다. getaddrinfo()가 두 개 이상의 주소를 반환하면 차례로 시도합니다. 생략하면 bind()를 호출하지 않습니다.
    • remote_addr: 소켓이 “연결”될 주소를 나타내는 선택적 튜플입니다. (데이터그램 연결이라는 것은 존재하지 않으므로, 이 값은 단지 나가는 데이터그램의 목적지 주소 기본값을 지정할 뿐입니다.) 지정하는 경우 반드시 (host, port) 쌍이어야 합니다. 이 값은 getaddrinfo()에 전달되어 해석되고, 그 결과는 생성된 소켓과 함께 sock_connect()에 전달됩니다. getaddrinfo()가 두 개 이상의 주소를 반환하면 차례로 시도합니다. 생략하면 sock_connect()를 호출하지 않습니다.

    <options>는 모두 선택적 키워드 인자로 지정합니다:

    • family, proto, flags: getaddrinfo()에 그대로 전달될 주소 패밀리, 프로토콜, 플래그입니다. 이들의 기본값은 모두 “지정되지 않음”을 뜻하는 0입니다. (소켓 타입은 항상 SOCK_DGRAM입니다.) 이 값들 중 지정되지 않은 것이 있으면 getaddrinfo() 메서드가 적절한 값을 선택합니다.

    local_addr와 remote_addr가 모두 주어지면, 주소 패밀리가 일치하는 로컬 주소와 원격 주소의 모든 조합을 시도한다는 점에 유의하십시오.

래핑된 소켓 메서드

소켓에서 비동기 I/O를 수행하는 다음 메서드들은 일반적인 용도로 쓰기 위한 것이 아닙니다. 이 메서드들은 주로 ProactorEventLoop 클래스를 통해 IOCP와 함께 동작하는 전송 구현을 위한 것입니다. 그러나 다른 이벤트 루프 유형에서도 쉽게 구현할 수 있으므로, 이를 요구하지 않을 이유가 없습니다. 소켓 인자는 논블로킹 소켓이어야 합니다.

  • sock_recv(sock, n). 소켓 sock에서 최대 n 바이트를 수신합니다. 성공 시 결과가 bytes 객체인 Future를 반환합니다.
  • sock_sendall(sock, data). 바이트 data를 소켓 sock으로 전송합니다. 성공 시 결과가 None인 Future를 반환합니다. 참고: 이 메서드의 의미와 시그니처가 표준 라이브러리 소켓 메서드 send()가 아니라 sendall()의 것을 따른다는 점을 나타내기 위해, 이름에 send 대신 sendall을 사용합니다.
  • sock_connect(sock, address). 주어진 주소에 연결합니다. 성공 시 결과가 None인 Future를 반환합니다.
  • sock_accept(sock). 소켓에서 연결을 수락합니다. 소켓은 리스닝 모드여야 하며 주소에 바인딩되어 있어야 합니다. 성공 시 결과가 튜플 (conn, peer)인 Future를 반환합니다. 여기서 conn은 연결된 논블로킹 소켓이고 peer는 피어 주소입니다.

I/O 콜백

이 메서드들은 주로 셀렉터와 함께 동작하는 전송 구현을 위한 것입니다. 이 메서드들은 SelectorEventLoop에는 구현되어 있지만 ProactorEventLoop에는 구현되어 있지 않습니다. 사용자 정의 이벤트 루프 구현은 이 메서드들을 구현할 수도 있고 구현하지 않을 수도 있습니다.

아래의 fd 인자는 정수 파일 디스크립터이거나, 정수 파일 디스크립터를 감싸는 fileno() 메서드를 가진 “파일류” 객체일 수 있습니다. 모든 파일류 객체나 파일 디스크립터가 허용되는 것은 아닙니다. 소켓(및 소켓 파일 디스크립터)은 항상 허용됩니다. Windows에서는 다른 유형이 지원되지 않습니다. UNIX에서는 파이프와 경우에 따라 tty 장치도 지원되지만, 디스크 파일은 지원되지 않습니다. 정확히 어떤 특수 파일 유형이 지원되는지는 플랫폼과 셀렉터 구현마다 다를 수 있습니다. (실험적으로 확인한 바에 따르면, OS X에는 select와 poll은 지원하지만 kqueue는 지원하지 않는 유사 tty가 적어도 한 종류 있으며, 이는 Emacs 셸 창(windows)에서 사용됩니다.)

  • add_reader(fd, callback, *args). 파일 디스크립터 fd가 읽기 준비가 되었다고 판단될 때마다 callback(*args)가 호출되도록 설정합니다. 같은 파일 디스크립터에 대해 add_reader()를 다시 호출하면, 같은 파일 디스크립터에 대해 remove_reader()를 호출하는 것과 같습니다.
  • add_writer(fd, callback, *args). add_reader()와 같지만, 읽기 대신 쓰기를 위한 콜백을 등록합니다.
  • remove_reader(fd). 파일 디스크립터 fd에 현재 읽기 콜백이 설정되어 있으면 이를 취소합니다. 해당 파일 디스크립터에 현재 설정된 콜백이 없으면 아무 동작도 하지 않고 False를 반환합니다. 그렇지 않으면 콜백 설정을 제거하고 True를 반환합니다.
  • remove_writer(fd). 이 메서드와 add_writer()의 관계는 remove_reader()와 add_reader()의 관계와 같습니다.

파이프와 서브프로세스

이 메서드들은 UNIX에서는 SelectorEventLoop가, Windows에서는 ProactorEventLoop가 지원합니다.

파이프와 서브프로세스에 사용되는 전송과 프로토콜은 일반 스트림 연결에 사용되는 것과 다릅니다. 이에 대해서는 뒤에서 설명합니다.

아래의 각 메서드에는 create_connection()과 비슷하게 protocol_factory 인자가 있습니다. 이 인자는 반환할 프로토콜 객체를 생성하기 위해 인자 없이 정확히 한 번 호출됩니다.

각 메서드는 성공하면 (transport, protocol) 쌍을 반환하고, 실패하면 예외를 발생시키는 코루틴입니다.

  • connect_read_pipe(protocol_factory, pipe): UNIX 파이프의 읽기 쪽 끝을 감싸는 파일류 객체로부터 단방향 스트림 연결을 생성합니다. 이 파이프는 논블로킹 모드여야 합니다. 반환되는 전송은 ReadTransport입니다.
  • connect_write_pipe(protocol_factory, pipe): UNIX 파이프의 쓰기 쪽 끝을 감싸는 파일류 객체로부터 단방향 스트림 연결을 생성합니다. 이 파이프는 논블로킹 모드여야 합니다. 반환되는 전송은 WriteTransport이며, 읽기 관련 메서드는 전혀 없습니다. 반환되는 프로토콜은 BaseProtocol입니다.
  • subprocess_shell(protocol_factory, cmd, <options>): 플랫폼의 “셸” 문법을 사용하는 문자열인 cmd로부터 서브프로세스를 생성합니다. 이는 shell=True로 호출한 표준 라이브러리 subprocess.Popen() 클래스와 비슷합니다. 나머지 인자와 반환값은 아래에서 설명합니다.
  • subprocess_exec(protocol_factory, *args, <options>): 하나 이상의 문자열 인자로부터 서브프로세스를 생성합니다. 첫 번째 문자열은 실행할 프로그램을 지정하고, 나머지 문자열은 프로그램의 인자를 지정합니다. (따라서 프로그램이 Python 스크립트라고 가정하면, 문자열 인자들이 모여 프로그램의 sys.argv 값을 이룹니다.) 이는 shell=False로, 그리고 문자열 리스트를 첫 번째 인자로 전달하여 호출한 표준 라이브러리 subprocess.Popen() 클래스와 비슷합니다. 다만 Popen()은 문자열 리스트인 단일 인자를 받는 반면, subprocess_exec()는 여러 개의 문자열 인자를 받습니다. 나머지 인자와 반환값은 아래에서 설명합니다.

실행할 프로그램을 지정하는 방식을 제외하면 두 subprocess_*() 메서드는 동일하게 동작합니다. 반환되는 전송은 일반적인 양방향 스트림 전송과는 다른 인터페이스를 가진 SubprocessTransport입니다. 반환되는 프로토콜은 마찬가지로 고유한 인터페이스를 가진 SubprocessProtocol입니다.

<options>는 모두 선택적 키워드 인자로 지정합니다:

  • stdin: connect_write_pipe()를 사용하여 서브프로세스의 표준 입력 스트림에 연결할 파이프를 나타내는 파일류 객체이거나, 상수 subprocess.PIPE(기본값)입니다. 기본적으로 새 파이프가 생성되어 연결됩니다.
  • stdout: connect_read_pipe()를 사용하여 서브프로세스의 표준 출력 스트림에 연결할 파이프를 나타내는 파일류 객체이거나, 상수 subprocess.PIPE(기본값)입니다. 기본적으로 새 파이프가 생성되어 연결됩니다.
  • stderr: connect_read_pipe()를 사용하여 서브프로세스의 표준 오류 스트림에 연결할 파이프를 나타내는 파일류 객체이거나, 상수 subprocess.PIPE (기본값) 또는 subprocess.STDOUT 중 하나입니다. 기본적으로 새 파이프가 만들어져 연결됩니다. subprocess.STDOUT을 지정하면 서브프로세스의 표준 오류 스트림은 표준 출력 스트림과 같은 파이프에 연결됩니다.
  • bufsize: 파이프를 만들 때 사용할 버퍼 크기이며, subprocess.Popen()에 전달됩니다. 기본 구현에서는 기본값이 0이며, Windows에서는 반드시 0이어야 합니다. 이 기본값들은 subprocess.Popen()의 기본값과 다릅니다.
  • executable, preexec_fn, close_fds, cwd, env, startupinfo, creationflags, restore_signals, start_new_session, pass_fds: 이 선택적 인자들은 해석 없이 그대로 subprocess.Popen()에 전달됩니다.

시그널 콜백

이 메서드들은 UNIX에서만 지원됩니다.

  • add_signal_handler(sig, callback, *args). 시그널 sig를 받을 때마다 callback(*args)가 호출되도록 합니다. 같은 시그널에 다른 콜백을 지정하면 이전 핸들러가 대체됩니다(시그널마다 하나의 핸들러만 활성화될 수 있습니다). sig는 signal 모듈에 정의된 유효한 시그널 번호여야 합니다. 시그널을 처리할 수 없으면 예외가 발생합니다. 유효한 시그널이 아니거나 붙잡을 수 없는 시그널(예: SIGKILL)이면 ValueError가 발생하고, 이 이벤트 루프 인스턴스가 시그널을 처리할 수 없으면 RuntimeError가 발생합니다(시그널은 프로세스 단위로 전역이므로 메인 스레드에 연결된 이벤트 루프만 시그널을 처리할 수 있습니다).
  • remove_signal_handler(sig). 시그널 sig에 핸들러가 설정되어 있으면 그 핸들러를 제거합니다. add_signal_handler()와 같은 예외를 발생시킵니다(단, 붙잡을 수 없는 시그널에 대해서는 RuntimeError를 발생시키는 대신 False를 반환할 수 있습니다). 핸들러를 성공적으로 제거했으면 True를, 설정된 핸들러가 없었으면 False를 반환합니다.

참고: 이 메서드들이 지원되지 않는다는 것을 정적으로 알 수 있는 경우에는 RuntimeError 대신 NotImplementedError를 발생시킬 수 있습니다.

콜백의 상호 배제

이벤트 루프는 콜백의 상호 배제를 보장해야 합니다. 즉, 이전 콜백이 아직 실행 중일 때 다른 콜백을 시작해서는 안 됩니다. 이는 call_soon(), call_later(), call_at(), call_soon_threadsafe(), add_reader(), add_writer(), add_signal_handler() 중 무엇으로 예약되었는지와 관계없이 모든 종류의 콜백에 적용되어야 합니다.

예외

Python의 예외는 두 범주로 나뉩니다. Exception 클래스에서 파생된 예외와 BaseException에서 파생된 예외입니다. Exception에서 파생된 예외는 일반적으로 붙잡혀 적절히 처리됩니다. 예를 들어 퓨처는 이런 예외를 그대로 전달하고, 콜백에서 발생한 이런 예외는 로그로 기록된 뒤 무시됩니다.

그러나 BaseException에서만 파생된 예외는 보통 붙잡히지 않으며, 대개 트레이스백을 남기고 프로그램을 종료시킵니다. 어떤 경우에는 이런 예외를 붙잡았다가 다시 발생시킵니다. (이 범주의 예로는 KeyboardInterrupt와 SystemExit가 있습니다. 이런 예외를 다른 대부분의 예외와 똑같이 다루는 것은 대개 현명하지 않습니다.)

이벤트 루프는 후자의 범주를 자신의 예외 핸들러에 전달합니다. 이것은 context 딕셔너리를 매개변수로 받는 콜백입니다:

def exception_handler(context):
    ...

context에는 여러 가지 키가 있을 수 있지만, 그중 몇 가지는 매우 널리 사용됩니다.

  • 'message': 오류 메시지입니다.
  • 'exception': 예외 인스턴스입니다. 예외가 없으면 None입니다.
  • 'source_traceback': 오류와 관련된 객체가 생성된 시점의 스택을 나타내는 문자열 리스트입니다.
  • 'handle_traceback': 오류와 관련된 핸들이 생성된 시점의 스택을 나타내는 문자열 리스트입니다.

루프에는 예외 처리와 관련된 다음 메서드가 있습니다:

  • get_exception_handler()는 루프에 등록된 현재 예외 핸들러를 반환합니다.
  • set_exception_handler(handler)는 예외 핸들러를 설정합니다.
  • default_exception_handler(context)는 이 루프 구현의 기본 예외 핸들러입니다.
  • call_exception_handler(context)는 context를 등록된 예외 핸들러에 전달합니다. 이를 통해 서드파티 라이브러리가 처리되지 않은 예외를 일관된 방식으로 처리할 수 있습니다.

    명시적인 set_exception_handler() 호출로 기본 처리기를 재정의하지 않은 경우, 루프는 default_exception_handler()를 사용합니다.

디버그 모드

기본적으로 루프는 릴리스 모드로 동작합니다. 애플리케이션은 약간의 성능을 대가로 더 나은 오류 보고를 위해 디버그 모드를 활성화할 수 있습니다.

디버그 모드에서는 여러 추가 검사가 활성화됩니다. 예를 들면 다음과 같습니다:

  • 퓨처/태스크에서 처리되지 않은 예외에 대해 소스 트레이스백을 확인할 수 있습니다.
  • 루프는 의도치 않은 I/O 블로킹을 감지하기 위해 느린 콜백을 검사합니다.

    loop.slow_callback_duration 속성은 느린 콜백으로 보고되기 전까지 두 yield 지점 사이에 허용되는 최대 실행 시간을 제어합니다. 기본값은 0.1초이며, 이 어트리뷰트에 값을 대입하여 변경할 수 있습니다.

디버그 모드와 관련된 메서드는 두 가지입니다:

  • get_debug()는 디버그 모드가 활성화되어 있으면 True를, 그렇지 않으면 False를 반환합니다.
  • set_debug(enabled)는 인자가 True이면 디버그 모드를 활성화합니다.

PYTHONASYNCIODEBUG 환경 변수가 정의되어 있고 비어 있지 않으면 디버그 모드가 자동으로 활성화됩니다.

핸들

일회성 콜백을 등록하는 여러 메서드(call_soon(), call_later(), call_at(), call_soon_threadsafe())는 모두 등록을 나타내는 객체를 반환하며, 이 객체는 콜백을 취소하는 데 사용할 수 있습니다. 이 객체를 Handle이라고 합니다. 핸들은 불투명하며 공개 메서드는 하나뿐입니다:

  • cancel(): 콜백을 취소합니다.

add_reader(), add_writer(), add_signal_handler()는 핸들을 반환하지 않는다는 점에 유의하십시오.

서버

create_server() 메서드는 요청을 수락하는 데 사용되는 소켓(또는 기타 네트워크 객체)을 감싸는 Server 인스턴스를 반환합니다. 이 클래스에는 공개 메서드가 두 가지 있습니다:

  • close(): 서비스를 닫습니다. 이 메서드는 새 요청의 수락을 중단하지만, 이미 수락되어 현재 처리 중인 요청은 취소하지 않습니다.
  • wait_closed(): 서비스가 닫히고 수락된 모든 요청의 처리가 끝날 때까지 블록하는 코루틴입니다.

퓨처

여기서 말하는 asyncio.Future 클래스는 PEP 3148에 명시된 concurrent.futures.Future 클래스와 의도적으로 비슷하게 만들었지만, 약간의 차이가 있습니다. 이 PEP에서 Future나 퓨처를 언급할 때는 concurrent.futures.Future를 명시적으로 언급하지 않는 한 asyncio.Future를 가리키는 것으로 이해해야 합니다. 지원되는 공개 API는 다음과 같으며, PEP 3148과의 차이점도 함께 표시합니다:

  • cancel(). Future가 이미 완료되었거나 취소되었으면 아무것도 하지 않고 False를 반환합니다. 그렇지 않으면 Future의 취소를 시도하고 True를 반환합니다. 취소 시도가 성공하면 결국 Future의 상태가 취소됨으로 바뀌고(따라서 cancelled()가 True를 반환하게 됩니다) 콜백이 스케줄됩니다. 일반 Future에서는 취소가 항상 즉시 성공합니다. 그러나 Task(아래 참조)에서는 태스크가 취소 시도를 무시하거나 지연할 수 있습니다.
  • cancelled(). Future가 성공적으로 취소되었으면 True를 반환합니다.
  • done(). Future가 완료되었으면 True를 반환합니다. 취소된 Future도 완료된 것으로 간주한다는 점에 유의하십시오(여기서뿐만 아니라 모든 곳에서 그렇습니다).
  • result(). set_result()로 설정된 결과를 반환하거나, set_exception()으로 설정된 예외를 발생시킵니다. 취소되었으면 CancelledError를 발생시킵니다. PEP 3148과의 차이점: 이 메서드는 타임아웃 인자가 없으며 대기하지 않습니다; 퓨처가 아직 완료되지 않았다면 예외를 발생시킵니다.
  • exception(). set_exception()으로 예외가 설정되었으면 그 예외를 반환하고, set_result()로 결과가 설정되었으면 None을 반환합니다. 취소되었으면 CancelledError를 발생시킵니다. PEP 3148과의 차이점: 이 메서드는 타임아웃 인자가 없으며 대기하지 않습니다; 퓨처가 아직 완료되지 않았다면 예외를 발생시킵니다.
  • add_done_callback(fn). Future가 완료되거나 취소될 때 실행할 콜백을 추가합니다. Future가 이미 완료되었거나 취소되었으면 call_soon()을 사용해 콜백을 스케줄합니다. PEP 3148과의 차이점: 콜백은 절대 즉시 호출되지 않으며, 항상 호출자의 컨텍스트에서 호출됩니다. 호출자의 컨텍스트는 보통 스레드입니다. 이는 call_soon()을 통해 콜백을 호출하는 것으로 생각할 수 있습니다. PEP 3148과 맞추기 위해, 이 콜백은 (이 PEP에서 정의하는 다른 모든 콜백과 달리, 그리고 아래 “콜백 스타일” 절의 관례와 상관없이) 항상 Future 객체 하나만 인자로 받아 호출된다는 점에 유의하십시오. (call_soon()으로 스케줄된 콜백을 엄격하게 직렬화하는 이유가 여기에도 똑같이 적용됩니다.)
  • remove_done_callback(fn). 콜백 목록에서 인자를 제거합니다. 이 메서드는 PEP 3148에 정의되어 있지 않습니다. 인자는 add_done_callback()에 전달한 인자와 (==로 비교했을 때) 같아야 합니다. 콜백이 제거된 횟수를 반환합니다.
  • set_result(result). Future는 이미 완료(또는 취소)된 상태가 아니어야 합니다. 이 메서드는 Future를 완료 상태로 만들고 콜백을 스케줄링합니다. PEP 3148과의 차이점: 이것은 공개 API입니다.
  • set_exception(exception). Future는 이미 완료(또는 취소)된 상태가 아니어야 합니다. 이 메서드는 Future를 완료 상태로 만들고 콜백을 스케줄링합니다. PEP 3148과의 차이점: 이것은 공개 API입니다.

내부 메서드 set_running_or_notify_cancel()은 지원되지 않습니다. 실행 중 상태를 설정할 방법은 없습니다. 마찬가지로 running() 메서드도 지원되지 않습니다.

다음 예외들이 정의되어 있습니다:

  • InvalidStateError. Future가 호출되는 메서드가 허용하는 상태가 아닐 때마다 발생합니다(예: 이미 완료된 Future에 set_result()를 호출하거나, 아직 완료되지 않은 Future에 result()를 호출하는 경우).
  • InvalidTimeoutError. 0이 아닌 timeout 인자가 주어졌을 때 result()와 exception()에서 발생합니다.
  • CancelledError. concurrent.futures.CancelledError의 별칭입니다. 취소된 Future에 대해 result()나 exception()을 호출할 때 발생합니다.
  • TimeoutError. concurrent.futures.TimeoutError의 별칭입니다. run_until_complete()에서 발생할 수 있습니다.

Future는 생성될 때 이벤트 루프와 연결됩니다.

asyncio.Future 객체는 concurrent.futures 패키지의 wait() 및 as_completed() 함수에 사용할 수 없습니다. 하지만 아래에서 설명하는 유사한 API인 asyncio.wait()와 asyncio.as_completed()가 있습니다.

asyncio.Future 객체는 코루틴에서 사용될 때 yield from 표현식에 사용할 수 있습니다. 이는 Future의 __iter__() 인터페이스를 통해 구현됩니다. 아래의 “코루틴과 스케줄러” 절을 참조하십시오.

Future가 가비지 수집될 때 연관된 예외가 있는데도 result()와 exception() 중 어느 것도 호출된 적이 없다면, 그 예외는 로그에 기록됩니다. (코루틴이 Future를 기다리려고 yield from을 사용하면, 코루틴이 재개될 때 그 Future의 result() 메서드가 호출됩니다.)

미래에는(말장난을 의도했습니다) asyncio.Future와 concurrent.futures.Future를 통합할 수도 있습니다. 예를 들어 후자에 yield from과 함께 동작하는 __iter__() 메서드를 추가하는 방식입니다. 아직 완료되지 않은 Future에 result() 등을 호출하여 실수로 이벤트 루프를 블로킹하는 일을 막기 위해, 블로킹 연산이 현재 스레드에서 이벤트 루프가 활성 상태임을 감지하고 블로킹하는 대신 예외를 발생시킬 수 있습니다. 그러나 현재 PEP는 Python 3.3 외에는 어떤 의존성도 두지 않으려고 하므로, concurrent.futures.Future를 변경하는 것은 당분간 고려 대상이 아닙니다.

Future와 관련된 공개 함수가 몇 가지 있습니다:

  • asyncio.async(arg). 이 함수는 코루틴 객체이거나 Future인 인자(즉, yield from과 함께 사용할 수 있는 모든 것)를 받아 Future를 반환합니다. 인자가 Future이면 변경 없이 그대로 반환하고, 코루틴 객체이면 이를 Task로 감쌉니다(Task가 Future의 서브클래스라는 점을 기억하십시오).
  • asyncio.wrap_future(future). 이 함수는 PEP 3148 Future(즉, concurrent.futures.Future의 인스턴스)를 받아 이벤트 루프와 호환되는 Future(즉, asyncio.Future 인스턴스)를 반환합니다.

전송

전송과 프로토콜은 Twisted와 PEP 3153의 영향을 강하게 받았습니다. 사용자가 전송을 직접 구현하거나 인스턴스화하는 일은 드뭅니다 – 그보다는 이벤트 루프가 전송을 설정하는 유틸리티 메서드를 제공합니다.

전송은 프로토콜과 함께 동작합니다. 프로토콜은 보통 사용되는 전송의 정확한 타입을 알지 못하거나 신경 쓰지 않고 작성되며, 전송은 매우 다양한 프로토콜과 함께 사용될 수 있습니다. 예를 들어, HTTP 클라이언트 프로토콜 구현은 일반 소켓 전송과 SSL/TLS 전송 중 어느 쪽과도 함께 사용될 수 있습니다. 일반 소켓 전송은 HTTP 외에도 여러 다른 프로토콜(예: SMTP, IMAP, POP, FTP, IRC, SPDY)과 함께 사용될 수 있습니다.

가장 흔한 전송 타입은 양방향 스트림 전송입니다. 단방향 스트림 전송(파이프에 사용)과 데이터그램 전송(create_datagram_endpoint() 메서드에서 사용)도 있습니다.

모든 전송에 대한 메서드

  • get_extra_info(name, default=None). 이 메서드는 전송에 관한 구현별 정보를 반환하는 범용 메서드입니다. 첫 번째 인자는 조회할 추가 필드의 이름입니다. 선택적인 두 번째 인자는 반환할 기본값입니다. 지원되는 추가 필드 이름은 구현 문서를 참조하십시오. 지원되지 않는 이름에 대해서는 항상 기본값이 반환됩니다.

양방향 스트림 전송

양방향 스트림 전송은 소켓이나 그와 유사한 것(예를 들어, 한 쌍의 UNIX 파이프나 SSL/TLS 연결) 위에 놓인 추상화입니다.

대부분의 연결은 비대칭적인 성격을 가집니다. 클라이언트와 서버는 보통 역할과 동작이 매우 다릅니다. 따라서 전송과 프로토콜 사이의 인터페이스도 비대칭적입니다. 프로토콜 관점에서 데이터 쓰기는 전송 객체의 write() 메서드를 호출하여 이루어집니다. 이 메서드는 데이터를 버퍼에 담고 즉시 반환합니다. 하지만 데이터 읽기에서는 전송이 더 능동적인 역할을 합니다. 소켓(또는 다른 데이터 소스)에서 데이터를 읽을 때마다 전송이 프로토콜의 data_received() 메서드를 호출합니다.

그럼에도 클라이언트와 서버 사이의 연결은 본질적으로 방향별로 하나씩 있는 스트림 한 쌍이므로, 양방향 스트림이 사용하는 전송과 프로토콜 사이의 인터페이스는 클라이언트와 서버에서 동일합니다.

양방향 스트림 전송에는 다음과 같은 공개 메서드가 있습니다:

  • write(data). 바이트를 씁니다. 인자는 bytes 객체여야 합니다. None을 반환합니다. 트랜스포트는 바이트를 자유롭게 버퍼링할 수 있지만, 결국에는 그 바이트가 반대편 끝의 개체로 전송되도록 해야 하며 스트림 동작을 유지해야 합니다. 즉, t.write(b'abc'); t.write(b'def')는 t.write(b'abcdef')와 동등하며, 다음과도 동등합니다:
    t.write(b'a')
    t.write(b'b')
    t.write(b'c')
    t.write(b'd')
    t.write(b'e')
    t.write(b'f')
    
  • writelines(iterable). 다음과 동등합니다:
    for data in iterable:
        self.write(data)
    
  • write_eof(). 연결의 쓰기 쪽 끝을 닫습니다. 이후에는 write()를 호출할 수 없습니다. 버퍼링된 데이터가 모두 전송되면, 트랜스포트는 더 이상 수신할 데이터가 없음을 반대편 끝에 알립니다. 일부 프로토콜은 이 연산을 지원하지 않습니다. 이 경우 write_eof()를 호출하면 예외가 발생합니다. (참고: 이 메서드는 예전에 half_close()라고 불렸지만, 그 용도를 이미 알고 있지 않다면 그 이름만으로는 어느 쪽 끝이 닫히는지 알 수 없습니다.)
  • can_write_eof(). 프로토콜이 write_eof()를 지원하면 True를, 지원하지 않으면 False를 반환합니다. (이 메서드는 보통 전송 객체의 상태가 아니라 특정 전송 클래스에만 의존하는 고정된 값을 반환합니다. 일부 프로토콜은 write_eof()를 사용할 수 없을 때 동작을 바꿔야 하므로 이 메서드가 필요합니다. 예를 들어 HTTP에서 크기를 미리 알 수 없는 데이터를 보낼 때는 일반적으로 write_eof()를 사용하여 데이터의 끝을 나타냅니다. 그러나 SSL/TLS는 이를 지원하지 않으므로, 이 경우 HTTP 프로토콜 구현은 “chunked” 전송 인코딩을 사용해야 합니다. 하지만 데이터 크기를 미리 알고 있다면, 두 경우 모두 Content-Length 헤더를 사용하는 것이 가장 좋은 방법입니다.)
  • get_write_buffer_size(). 전송의 쓰기 버퍼의 현재 크기를 바이트 단위로 반환합니다. 이 메서드는 전송이 명시적으로 관리하는 쓰기 버퍼만 알고 있으며, 네트워크 스택의 다른 계층이나 네트워크의 다른 곳에서 이루어지는 버퍼링은 보고하지 않습니다.
  • set_write_buffer_limits(high=None, low=None). 흐름 제어를 위한 상한(high-water) 및 하한(low-water) 한계를 설정합니다.

    이 두 값은 프로토콜의 pause_writing() 및 resume_writing() 메서드를 언제 호출할지 제어합니다. 지정하는 경우, 하한 한계는 상한 한계보다 작거나 같아야 합니다. 두 값 모두 음수일 수 없습니다.

    기본값은 구현에 따라 다릅니다. 상한만 주어지면 하한은 상한보다 작거나 같은 구현별 값으로 기본 설정됩니다. high를 0으로 설정하면 low도 강제로 0이 되며, 버퍼가 비어 있지 않게 될 때마다 pause_writing()이 호출됩니다. low를 0으로 설정하면 버퍼가 비었을 때에만 resume_writing()이 호출됩니다. 어느 한도든 0을 사용하면 I/O와 계산을 동시에 수행할 기회가 줄어들므로 일반적으로 최적이 아닙니다.

  • pause_reading(). 이후 resume_reading() 호출이 있을 때까지 프로토콜로의 데이터 전달을 일시 중지합니다. pause_reading()과 resume_reading() 사이에는 프로토콜의 data_received() 메서드가 호출되지 않습니다.
  • resume_reading(). data_received()를 통해 프로토콜로의 데이터 전달을 재개합니다. “일시 중지됨”은 이진 상태라는 점에 유의하십시오 – pause_reading()은 전송이 일시 중지되지 않았을 때만 호출해야 하고, resume_reading()은 전송이 일시 중지되었을 때만 호출해야 합니다.
  • close(). 반대편 끝에 있는 개체와의 연결을 끊습니다. write()로 버퍼링된 모든 데이터는 연결이 실제로 닫히기 전에 (결국) 전송됩니다. 프로토콜의 data_received() 메서드는 다시 호출되지 않습니다. 버퍼링된 모든 데이터가 플러시되면 프로토콜의 connection_lost() 메서드가 None을 인자로 하여 호출됩니다. 이 메서드는 이 모든 일이 일어나기를 기다리지 않는다는 점에 유의하십시오.
  • abort(). 연결을 즉시 끊습니다. 전송에 아직 버퍼링되어 있는 데이터는 모두 버려집니다. 곧 프로토콜의 connection_lost() 메서드가 None을 인자로 하여 호출됩니다.

단방향 스트림 전송

쓰기 스트림 전송은 양방향 스트림 전송에서 설명한 write(), writelines(), write_eof(), can_write_eof(), close(), abort() 메서드를 지원합니다.

읽기 스트림 전송은 양방향 스트림 전송에서 설명한 pause_reading(), resume_reading(), close() 메서드를 지원합니다.

쓰기 스트림 전송은 연결된 프로토콜에 대해 connection_made()와 connection_lost()만 호출합니다.

읽기 스트림 전송은 아래 프로토콜 절에 명시된 모든 프로토콜 메서드(즉, 앞의 두 메서드에 data_received()와 eof_received()를 더한 것)를 호출할 수 있습니다.

데이터그램 전송

데이터그램 전송에는 다음 메서드가 있습니다:

  • sendto(data, addr=None). 데이터그램(bytes 객체)을 전송합니다. 선택적인 두 번째 인자는 목적지 주소입니다. 생략하면 이 전송을 생성한 create_datagram_endpoint() 호출에서 remote_addr가 지정되어 있어야 합니다. 주어졌고 remote_addr도 지정되어 있었다면, 둘은 일치해야 합니다. (data, addr) 쌍은 즉시 전송되거나 버퍼링될 수 있습니다. 반환값은 None입니다.
  • abort(). 전송을 즉시 닫습니다. 버퍼링된 데이터는 버려집니다.
  • close(). 전송을 닫습니다. 버퍼링된 데이터는 비동기적으로 전송됩니다.

데이터그램 전송은 연결된 프로토콜 객체에 대해 다음 메서드를 호출합니다: connection_made(), connection_lost(), error_received(), datagram_received(). (이 메서드 이름에 있는 “Connection”(연결)은 약간 잘못된 명칭이지만, 개념 자체는 여전히 존재합니다: connection_made()는 엔드포인트를 나타내는 전송이 생성되었음을 의미하고, connection_lost()는 전송이 닫혔음을 의미합니다.)

서브프로세스 전송

서브프로세스 전송에는 다음과 같은 메서드가 있습니다:

  • get_pid(). 서브프로세스의 프로세스 ID를 반환합니다.
  • get_returncode(). 프로세스가 종료되었으면 프로세스 반환 코드를 반환하고, 그렇지 않으면 None을 반환합니다.
  • get_pipe_transport(fd). 인자에 해당하는 파이프 전송(단방향 스트림 전송)을 반환합니다. 인자는 (서브프로세스의) stdin, stdout, stderr를 나타내는 0, 1, 2 중 하나여야 합니다. 해당하는 파이프 전송이 없으면 None을 반환합니다. stdin의 경우 쓰기 전송이고, stdout과 stderr의 경우 읽기 전송입니다. 서브프로세스의 stdin에 쓰는 데 사용할 전송을 얻으려면 반드시 이 메서드를 사용해야 합니다.
  • send_signal(signal). 서브프로세스에 시그널을 보냅니다.
  • terminate(). 서브프로세스를 종료합니다.
  • kill(). 서브프로세스를 강제 종료합니다. Windows에서는 terminate()의 별칭입니다.
  • close(). 이것은 terminate()의 별칭입니다.

send_signal(), terminate(), kill()은 표준 라이브러리 subprocess 모듈의 해당 메서드를 감싼 것이라는 점에 유의하십시오.

프로토콜

프로토콜은 항상 전송과 함께 사용됩니다. 몇 가지 일반적인 프로토콜(예: 훌륭하다고까지는 할 수 없어도 쓸 만한 HTTP 클라이언트 및 서버 구현)이 제공되지만, 대부분의 프로토콜은 사용자 코드나 서드파티 라이브러리에서 구현할 것입니다.

전송과 마찬가지로, 스트림 프로토콜, 데이터그램 프로토콜, 그리고 경우에 따라 그 밖의 사용자 정의 프로토콜을 구분합니다. 가장 일반적인 프로토콜 유형은 양방향 스트림 프로토콜입니다. (단방향 프로토콜은 없습니다.)

스트림 프로토콜

(양방향) 스트림 프로토콜은 전송이 호출하게 될 다음 메서드를 구현해야 합니다. 이 메서드들은 항상 이벤트 루프가 올바른 컨텍스트에서 호출하는 콜백이라고 생각하십시오. (한참 앞의 “컨텍스트” 절을 참조하십시오.)

  • connection_made(transport). 전송이 준비되었고 반대편 끝의 개체와 연결되었음을 나타냅니다. 프로토콜은 (나중에 전송의 write() 및 기타 메서드를 호출할 수 있도록) 전송 참조를 인스턴스 변수로 저장해 두는 것이 좋으며, 이 시점에 초기 인사말이나 요청을 쓸 수도 있습니다.
  • data_received(data). 전송이 연결에서 몇 바이트를 읽었습니다. 인자는 항상 비어 있지 않은 bytes 객체입니다. 이런 방식으로 전달되는 데이터의 최소 또는 최대 크기에 대해서는 아무런 보장이 없습니다. p.data_received(b'abcdef')는 다음과 정확히 동등하게 취급되어야 합니다:
    p.data_received(b'abc')
    p.data_received(b'def')
    
  • eof_received(). 이 메서드는 반대편 끝이 write_eof()(또는 이에 상응하는 것)를 호출했을 때 호출됩니다. 이 메서드가 거짓 값(None 포함)을 반환하면 전송은 스스로 닫힙니다. 참 값을 반환하면 전송을 닫는 일은 프로토콜이 맡습니다. 그러나 SSL/TLS 연결에서는 이 반환 값이 무시됩니다. TLS 표준은 “closure alert”를 받는 즉시 더 이상 데이터를 보내지 않고 연결을 닫도록 요구하기 때문입니다.

    기본 구현은 None을 반환합니다.

  • pause_writing(). 프로토콜에 전송으로 데이터를 쓰는 것을 잠시 멈추도록 요청합니다. 이 요청을 따를지는 선택 사항이지만, 계속 쓰면 전송의 버퍼가 한없이 커질 수 있습니다. 이 메서드가 호출되는 버퍼 크기는 전송의 set_write_buffer_limits() 메서드로 조절할 수 있습니다.
  • resume_writing(). 프로토콜에 전송으로 다시 데이터를 써도 안전하다고 알립니다. 이 메서드는 (call_soon()을 사용해 간접적으로 호출되는 대신) 전송의 write() 메서드에서 직접 호출될 수 있다는 점에 유의하십시오. 그래야 프로토콜이 write()가 반환된 직후에 자신이 일시 정지 상태임을 알 수 있습니다.
  • connection_lost(exc). 전송이 닫혔거나 중단되었거나, 상대편이 연결을 정상적으로 닫았음을 감지했거나, 예기치 않은 오류를 만났습니다. 앞의 세 경우에는 인자가 None이고, 예기치 않은 오류인 경우에는 인자가 전송을 포기하게 만든 예외입니다.

다음은 기본 호출의 순서와 횟수를 나타낸 표입니다:

  1. connection_made() – 정확히 한 번
  2. data_received() – 0회 이상
  3. eof_received() – 최대 한 번
  4. connection_lost() – 정확히 한 번

pause_writing()과 resume_writing() 호출은 쌍으로 발생하며, #1과 #4 사이에서만 발생합니다. 이 쌍은 중첩되지 않습니다. 마지막 resume_writing() 호출은 생략될 수 있습니다. 즉, 일시 중지된 연결이 재개되지 않은 채 끊어질 수 있습니다.

데이터그램 프로토콜

데이터그램 프로토콜에도 스트림 프로토콜과 시그니처가 같은 connection_made() 및 connection_lost() 메서드가 있습니다. (데이터그램 전송에 관한 절에서 설명했듯이, 소켓이 열리고 닫히는 것을 나타내려고 메서드 이름을 따로 정의하기보다는 조금 어색하더라도 이 명명법을 쓰는 편을 택했습니다.)

이 외에도 다음 메서드가 있습니다:

  • datagram_received(data, addr). 원격 주소 addr(IPv4 2-튜플 또는 IPv6 4-튜플)로부터 데이터그램 data(bytes 객체)를 수신했음을 나타냅니다.
  • error_received(exc). 송신 또는 수신 작업에서 OSError 예외가 발생했음을 나타냅니다. 데이터그램 오류는 일시적일 수 있으므로, 엔드포인트를 닫으려면 프로토콜이 직접 전송의 close() 메서드를 호출해야 합니다.

다음은 호출의 순서와 횟수를 나타낸 도표입니다:

  1. connection_made() – 정확히 한 번
  2. datagram_received(), error_received() – 0회 이상
  3. connection_lost() – 정확히 한 번

서브프로세스 프로토콜

서브프로세스 프로토콜에는 스트림 프로토콜과 시그니처가 같은 connection_made(), connection_lost(), pause_writing(), resume_writing() 메서드가 있습니다. 이 외에도 다음 메서드가 있습니다:

  • pipe_data_received(fd, data). 서브프로세스가 stdout 또는 stderr에 데이터를 쓸 때 호출됩니다. fd는 파일 디스크립터입니다(stdout은 1, stderr는 2). data는 bytes 객체입니다.
  • pipe_connection_lost(fd, exc). 서브프로세스가 stdin, stdout 또는 stderr를 닫을 때 호출됩니다. fd는 파일 디스크립터입니다. exc는 예외 또는 None입니다.
  • process_exited(). 서브프로세스가 종료되었을 때 호출됩니다. 종료 상태를 얻으려면 전송의 get_returncode() 메서드를 사용하십시오.

서브프로세스의 동작에 따라 process_exited()가 pipe_connection_lost()보다 먼저 호출될 수도 있고 나중에 호출될 수도 있다는 점에 유의하십시오. 예를 들어 서브프로세스가 자신의 stdin/stdout/stderr를 공유하는 하위 서브프로세스를 만든 다음 자신은 종료하면, 모든 파이프가 아직 열려 있는 상태에서 process_exited()가 호출될 수 있습니다. 반면에 서브프로세스가 자신의 stdin/stdout/stderr를 닫았지만 종료하지는 않은 경우에는 process_exited()가 호출되지 않은 채 세 파이프 모두에 대해 pipe_connection_lost()가 호출될 수 있습니다. (더 흔한 경우처럼) 서브프로세스가 종료되면서 암묵적으로 모든 파이프를 닫는 경우, 호출 순서는 정의되어 있지 않습니다.

콜백 스타일

콜백을 받는 대부분의 인터페이스는 위치 인자도 받습니다. 예를 들어 foo("abc", 42)가 곧 호출되도록 하려면 loop.call_soon(foo, "abc", 42)를 호출합니다. foo() 호출을 예약하려면 loop.call_soon(foo)를 사용하십시오. 이 관례 덕분에 일반적인 콜백 프로그래밍에서 필요한 작은 람다의 수가 크게 줄어듭니다.

이 관례는 특히 키워드 인자를 지원하지 않습니다. 키워드 인자는 콜백에 관한 선택적인 추가 정보를 전달하는 데 사용됩니다. 이렇게 하면 어떤 키워드가 어딘가의 피호출자에게 의미가 있을지 걱정할 필요 없이 API를 매끄럽게 발전시킬 수 있습니다. 키워드 인자와 함께 반드시 호출되어야 하는 콜백이 있다면 람다를 사용할 수 있습니다. 예를 들면 다음과 같습니다.:

loop.call_soon(lambda: foo('abc', repeat=42))

코루틴과 스케줄러

이 부분은 이벤트 루프 인터페이스와 위상이 다르기 때문에 별도의 최상위 섹션으로 두었습니다. 코루틴 사용은 선택 사항이며, 콜백만 사용하여 코드를 작성해도 전혀 문제가 없습니다. 반면에 스케줄러/코루틴 API의 구현은 하나뿐이며, 코루틴을 사용하고 있다면 바로 그 구현을 사용하고 있는 것입니다.

코루틴

코루틴은 특정 관례를 따르는 제너레이터입니다. 문서화를 위해 모든 코루틴은 @asyncio.coroutine으로 데코레이트해야 하지만, 이를 엄격하게 강제할 수는 없습니다.

코루틴은 원래의 yield 문법 대신 PEP 380에서 도입된 yield from 문법을 사용합니다.

“코루틴”이라는 단어는 “제너레이터”라는 단어와 마찬가지로 서로 다른(그러나 연관된) 두 가지 개념에 사용됩니다.

  • 코루틴을 정의하는 함수(asyncio.coroutine으로 데코레이트된 함수 정의)입니다. 구분이 필요한 경우 이를 코루틴 함수라고 부르겠습니다.
  • 코루틴 함수를 호출하여 얻은 객체입니다. 이 객체는 결국 완료될 계산이나 I/O 연산(보통은 둘의 조합)을 나타냅니다. 구분이 필요한 경우 이를 코루틴 객체라고 부르겠습니다.

코루틴이 할 수 있는 일은 다음과 같습니다.

  • result = yield from future – future가 완료될 때까지 코루틴을 일시 중단한 다음, future의 결과를 반환하거나 예외를 발생시키며, 이 예외는 전파됩니다. (future가 취소되면 CancelledError 예외를 발생시킵니다.) 태스크도 future이며, future에 대해 설명한 모든 내용이 태스크에도 적용된다는 점에 유의하십시오.
  • result = yield from coroutine – 다른 코루틴이 결과를 만들어 낼 때까지 기다립니다(또는 예외를 발생시키며, 이 예외는 전파됩니다). coroutine 표현식은 반드시 다른 코루틴의 호출이어야 합니다.
  • return expression – yield from을 사용하여 이 코루틴을 기다리고 있는 코루틴에 결과를 돌려줍니다.
  • raise exception – yield from을 사용하여 이 코루틴을 기다리고 있는 코루틴에서 예외를 발생시킵니다.

코루틴을 호출해도 그 코드가 실행되기 시작하지는 않습니다 – 코루틴은 단지 제너레이터일 뿐이며, 호출이 반환하는 코루틴 객체는 실제로는 제너레이터 객체이므로 이를 이터레이션하기 전까지는 아무 일도 하지 않습니다. 코루틴 객체의 경우, 실행을 시작하는 기본적인 방법은 두 가지입니다: 다른 코루틴에서 yield from coroutine을 호출하거나(그 다른 코루틴이 이미 실행 중이라고 가정합니다!), 코루틴을 Task로 변환하는 것입니다(아래 참조).

코루틴(과 태스크)은 이벤트 루프가 실행 중일 때만 실행될 수 있습니다.

여러 코루틴 기다리기

여러 코루틴이나 Future를 기다리기 위해, concurrent.futures 패키지의 wait() 및 as_completed() API와 유사한 두 가지 API가 제공됩니다:

  • asyncio.wait(fs, timeout=None, return_when=ALL_COMPLETED). 이것은 fs로 주어진 Future나 코루틴이 완료되기를 기다리는 코루틴입니다. 코루틴 인자는 Task로 래핑됩니다(아래 참조). 이것은 성공 시 결과가 두 개의 Future 집합으로 이루어진 튜플 (done, pending)인 Future를 반환합니다. 여기서 done은 완료된(또는 취소된) 원래 Future(또는 래핑된 코루틴)의 집합이고, pending은 나머지, 즉 아직 완료되지 않은(취소되지도 않은) 것들입니다. timeout과 return_when의 기본값을 사용하면 done은 항상 빈 리스트가 된다는 점에 유의하십시오. 선택적 인자 timeout과 return_when은 concurrent.futures.wait()에서와 같은 의미와 기본값을 가집니다: timeout은 None이 아니면 전체 작업의 타임아웃을 지정하고, return_when은 언제 멈출지를 지정합니다. 상수 FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED는 PEP 3148에서와 같은 값과 같은 의미로 정의됩니다:
    • ALL_COMPLETED (기본값): 모든 Future가 완료될 때까지(또는 타임아웃이 발생할 때까지) 기다립니다.
    • FIRST_COMPLETED: 적어도 하나의 Future가 완료될 때까지(또는 타임아웃이 발생할 때까지) 기다립니다.
    • FIRST_EXCEPTION: 적어도 하나의 Future가 취소되지 않고 예외가 설정된 채로 완료될 때까지 기다립니다. (취소된 Future를 조건에서 제외하는 것은 의외이지만, PEP 3148이 이렇게 합니다.)
  • asyncio.as_completed(fs, timeout=None). 값이 Future나 코루틴인 이터레이터를 반환합니다. 연속되는 값을 기다리면 집합 fs에서 다음 Future나 코루틴이 완료될 때까지 기다린 뒤, 그 결과를 반환합니다(또는 그 예외를 발생시킵니다). 선택적 인자 timeout은 concurrent.futures.wait()에서와 같은 의미와 기본값을 가집니다: 타임아웃이 발생하면, 이터레이터가 반환하는 다음 Future는 기다릴 때 TimeoutError를 발생시킵니다. 사용 예:
    for f in as_completed(fs):
        result = yield from f  # May raise an exception.
        # Use result.
    

    참고: 이터레이터가 만들어 내는 값을 기다리지 않으면, for 루프가 진행되지 않을 수 있습니다(다른 태스크가 실행될 기회를 주지 않기 때문입니다).

  • asyncio.wait_for(f, timeout). 이것은 단일 코루틴이나 Future를 타임아웃과 함께 기다리기 위한 편의 기능입니다. 타임아웃이 발생하면, 태스크를 취소하고 TimeoutError를 발생시킵니다. 태스크가 취소되지 않게 하려면 shield()로 감싸십시오.
  • asyncio.gather(f1, f2, ...). 모든 인자(Future 또는 코루틴)가 완료될 때까지 기다린 뒤, 각 인자에 대응하는 결과의 리스트를 반환하는 Future를 반환합니다. 인자 중 하나 이상이 취소되거나 예외를 발생시키면, 반환된 Future는 (첫 번째 인자에 일어난 일에 맞추어) 취소되거나 예외가 설정되며, 나머지 인자는 백그라운드에서 계속 실행되도록 남겨집니다. 반환된 Future를 취소해도 인자에는 영향을 주지 않습니다. 코루틴 인자는 asyncio.async()를 사용하여 Future로 변환된다는 점에 유의하십시오.
  • asyncio.shield(f). Future를 취소로부터 보호하면서 기다립니다. 이 함수는 결과나 예외가 인자와 정확히 같은 Future를 반환합니다. 그러나 반환된 Future가 취소되더라도 인자로 받은 Future는 영향을 받지 않습니다.

    이 함수의 사용 사례로는 HTTP 서버에서 요청을 처리하는 코루틴을 위해 쿼리 결과를 캐시하는 코루틴을 들 수 있습니다. 클라이언트가 요청을 취소했을 때, 클라이언트가 다시 연결하면 쿼리 결과가 (바라건대) 캐시되어 있도록 쿼리를 캐시하는 코루틴은 계속 실행되기를 (논란의 여지는 있지만) 원할 수 있습니다. 예를 들어 다음과 같이 작성할 수 있습니다.:

    @asyncio.coroutine
    def handle_request(self, request):
        ...
        cached_query = self.get_cache(...)
        if cached_query is None:
            cached_query = yield from asyncio.shield(self.fill_cache(...))
        ...
    

슬립

코루틴 asyncio.sleep(delay)는 주어진 시간만큼 지연된 후에 반환합니다.

태스크

Task는 독립적으로 실행되는 코루틴을 관리하는 객체입니다. Task 인터페이스는 Future 인터페이스와 같으며, 실제로 Task는 Future의 서브클래스입니다. 태스크는 그 코루틴이 반환하거나 예외를 발생시킬 때 완료됩니다. 코루틴이 결과를 반환하면 그것이 태스크의 결과가 되고, 예외를 발생시키면 그것이 태스크의 예외가 됩니다.

아직 완료되지 않은 태스크를 취소하면 코루틴 안으로 asyncio.CancelledError 예외가 던져집니다. 코루틴이 이 예외를 잡지 않으면(또는 다시 발생시키면) 태스크는 취소된 것으로 표시됩니다(즉, cancelled()가 True를 반환합니다). 그러나 코루틴이 어떤 식으로든 이 예외를 잡아서 무시하면 계속 실행될 수 있습니다(그리고 cancelled()는 False를 반환합니다).

태스크는 코루틴과 Twisted 같은 콜백 기반 프레임워크 사이의 상호 운용에도 유용합니다. 코루틴을 Task로 변환한 뒤에는 그 Task에 콜백을 추가할 수 있습니다.

코루틴을 태스크로 변환하려면 코루틴 함수를 호출하고, 그 결과로 얻은 코루틴 객체를 loop.create_task() 메서드에 전달하십시오. 이 목적으로 asyncio.ensure_future()를 사용할 수도 있습니다.

왜 모든 코루틴을 자동으로 Task로 변환하지 않는지 궁금할 수도 있습니다. @asyncio.coroutine 데코레이터가 이를 수행할 수는 있습니다. 그러나 그렇게 하면 한 코루틴이 다른 코루틴을 호출하는(그리고 그런 호출이 계속 이어지는) 경우에 속도가 상당히 느려집니다. “맨(bare)” 코루틴으로 전환하는 것이 Task로 전환하는 것보다 오버헤드가 훨씬 적기 때문입니다.

Task 클래스는 Future에서 파생되었으며 다음과 같은 새 메서드를 추가합니다.

  • current_task(loop=None). 이벤트 루프에서 현재 실행 중인 태스크를 반환하는 클래스 메서드입니다. loop가 None이면 이 메서드는 기본 루프의 현재 태스크를 반환합니다. 모든 코루틴은 태스크 컨텍스트 안에서 실행됩니다. 이 컨텍스트는 ensure_future()나 loop.create_task()를 사용해 생성된 Task이거나, yield from이나 await를 사용해 다른 코루틴에서 호출됨으로써 주어지는 것입니다. 이 메서드는 코루틴 바깥에서, 예를 들어 loop.call_later()를 사용해 예약된 콜백 안에서 호출되면 None을 반환합니다.
  • all_tasks(loop=None). 루프의 모든 활성 태스크 집합을 반환하는 클래스 메서드입니다. loop가 None이면 기본 루프를 사용합니다.

스케줄러

스케줄러에는 공개 인터페이스가 없습니다. yield from future와 yield from task를 사용하여 스케줄러와 상호 작용합니다. 사실 스케줄러를 나타내는 단일 객체는 없습니다 – 스케줄러의 동작은 Task와 Future 클래스가 이벤트 루프의 공개 인터페이스만을 사용하여 구현하므로, 서드파티 이벤트 루프 구현에서도 동작합니다.

편의 유틸리티

FTP나 HTTP 같은 기본적인 스트림 기반 클라이언트와 서버를 간단하게 작성할 수 있도록 몇 가지 함수와 클래스를 제공합니다. 그 목록은 다음과 같습니다:

  • asyncio.open_connection(host, port): EventLoop.create_connection()의 래퍼로, Protocol 팩토리나 클래스를 제공할 필요가 없습니다. (reader, writer) 쌍을 반환하는 코루틴이며, 여기서 reader는 StreamReader의 인스턴스이고 writer는 StreamWriter의 인스턴스입니다(둘 다 아래에서 설명합니다).
  • asyncio.start_server(client_connected_cb, host, port): Protocol 팩토리나 클래스 대신 간단한 콜백 함수를 받는 EventLoop.create_server()의 래퍼입니다. create_server()와 마찬가지로 Server 객체를 반환하는 코루틴입니다. 클라이언트 연결이 수락될 때마다 client_connected_cb(reader, writer)가 호출되며, 여기서 reader는 StreamReader의 인스턴스이고 writer는 StreamWriter의 인스턴스입니다(둘 다 아래에서 설명합니다). client_connected_cb()가 반환한 결과가 코루틴이면 자동으로 Task로 감싸집니다.
  • StreamReader: 읽기 전용 바이너리 스트림과 크게 다르지 않은 인터페이스를 제공하는 클래스이며, 다만 여러 읽기 메서드가 코루틴이라는 점이 다릅니다. 보통 StreamReaderProtocol 인스턴스가 이 클래스를 구동합니다. 리더는 하나만 있어야 한다는 점에 유의하십시오. 리더의 인터페이스는 다음과 같습니다:
    • readline(): '\n'으로 끝나는 텍스트 한 줄을 나타내는 바이트 문자열을 읽는 코루틴입니다. 줄 끝과 스트림 끝 중 먼저 나오는 곳까지 읽습니다.
    • read(n): 최대 n 바이트를 읽는 코루틴입니다. n을 생략하거나 음수로 지정하면 스트림 끝까지 읽습니다.
    • readexactly(n): 정확히 n 바이트를 읽되, 그보다 스트림 끝이 먼저 오면 스트림 끝까지 읽는 코루틴입니다.
    • exception(): set_exception()을 사용하여 스트림에 설정된 예외를 반환하며, 설정된 예외가 없으면 None을 반환합니다.

    드라이버의 인터페이스는 다음과 같습니다:

    • feed_data(data): data(bytes 객체)를 내부 버퍼에 추가합니다. 리더의 계약을 충족할 만큼 충분한 데이터를 제공하면, 블록된 읽기 코루틴의 블록을 해제합니다.
    • feed_eof(): 버퍼의 끝을 알립니다. 블록된 읽기 코루틴의 블록을 해제합니다. 이 호출 이후에는 리더에 더 이상 데이터를 공급해서는 안 됩니다.
    • set_exception(exc): 스트림에 예외를 설정합니다. 이후의 모든 읽기 메서드는 이 예외를 발생시킵니다. 이 호출 이후에는 리더에 더 이상 데이터를 공급해서는 안 됩니다.
  • StreamWriter: 쓰기 전용 바이너리 스트림과 비슷한 인터페이스를 제공하는 클래스입니다. 이 클래스는 전송을 감쌉니다. 이 인터페이스는 전송 인터페이스의 일부를 확장한 것입니다. 다음 메서드는 대응하는 전송 메서드와 똑같이 동작합니다: write(), writelines(), write_eof(), can_write_eof(), get_extra_info(), close(). 쓰기 메서드는 코루틴이 _아니라는_ 점에 유의하십시오(이는 전송과 같지만 StreamReader 클래스와는 다릅니다). 다음 메서드는 전송 인터페이스에 추가된 것입니다:
    • drain(): 흐름 제어를 위해 상당한 양의 데이터를 쓴 후에는 이 메서드를 yield from과 함께 호출해야 합니다. 의도된 사용법은 다음과 같습니다:
      writer.write(data)
      yield from writer.drain()
      

      이 메서드는 엄밀히 말해 코루틴이 아니라는 점에 유의하십시오. 이 메서드는 Future 또는 빈 튜플을 반환합니다(둘 다 yield from에 전달할 수 있습니다). 이 메서드의 사용은 선택 사항입니다. 하지만 이 메서드를 사용하지 않으면, StreamWriter 아래에 있는 전송의 내부 버퍼가 지금까지 writer에 쓰인 모든 데이터로 가득 찰 수 있습니다. 앱이 쓰는 데이터의 양에 엄격한 제한을 두지 않는다면, 전송 버퍼가 가득 차지 않도록 가끔씩 yield from drain()을 _반드시_ 호출해야 합니다.

  • StreamReaderProtocol: 양방향 스트림 전송/프로토콜 인터페이스와 StreamReader 및 StreamWriter 클래스 사이에서 어댑터로 쓰이는 프로토콜 구현입니다. 이 클래스는 특정 StreamReader 인스턴스의 드라이버 역할을 하며, 다양한 프로토콜 콜백에 대응하여 해당 인스턴스의 메서드 feed_data(), feed_eof(), set_exception()을 호출합니다. 또한 StreamWriter 인스턴스의 drain() 메서드의 동작도 제어합니다.

동기화

threading 모듈의 것을 본뜬 잠금, 이벤트, 조건 변수, 세마포어가 구현되어 있으며, asyncio.locks 하위 모듈을 임포트하여 사용할 수 있습니다. queue 모듈의 것을 본뜬 큐가 구현되어 있으며, asyncio.queues 하위 모듈을 임포트하여 사용할 수 있습니다.

일반적으로 이들은 스레드 기반의 대응물과 긴밀하게 대응하지만, 블로킹 메서드(예: 잠금의 acquire(), 큐의 put()과 get())는 코루틴이며 타임아웃 매개변수는 제공되지 않습니다(다만 asyncio.wait_for()를 사용하여 블로킹 호출에 타임아웃을 추가할 수 있습니다).

모듈의 독스트링에 더 완전한 문서가 있습니다.

잠금

다음 클래스는 asyncio.locks에서 제공됩니다. Event를 제외한 이 모든 클래스에서는 다음과 같이 with 문을 yield from과 함께 사용하여 잠금을 획득하고, with 블록을 어떻게 벗어나든 잠금이 해제되도록 보장할 수 있습니다:

with (yield from my_lock):
    ...
  • Lock: 기본 뮤텍스이며, acquire()(코루틴), locked(), release() 메서드를 가집니다.
  • Event: 이벤트 변수이며, wait()(코루틴), set(), clear(), is_set() 메서드를 가집니다.
  • Condition: 조건 변수이며, acquire(), wait(), wait_for(predicate)(세 가지 모두 코루틴), locked(), release(), notify(), notify_all() 메서드를 가집니다.
  • Semaphore: 세마포어이며, acquire()(코루틴), locked(), release() 메서드를 가집니다. 생성자 인자는 초기값입니다(기본값 1).
  • BoundedSemaphore: 상한이 있는 세마포어입니다. Semaphore와 비슷하지만 초기값이 곧 최댓값이기도 합니다.

큐

다음 클래스와 예외는 asyncio.queues에서 제공합니다.

  • Queue: get(), put()(둘 다 코루틴), get_nowait(), put_nowait(), empty(), full(), qsize(), maxsize() 메서드를 제공하는 표준 큐입니다.
  • PriorityQueue: 우선순위 순서(가장 낮은 것부터)로 항목을 꺼내는 Queue의 서브클래스입니다.
  • LifoQueue: 가장 최근에 추가된 항목부터 꺼내는 Queue의 서브클래스입니다.
  • JoinableQueue: task_done()과 join() 메서드(후자는 코루틴)를 제공하는 Queue의 서브클래스입니다.
  • Empty, Full: 각각 비어 있거나 가득 찬 큐에서 get_nowait() 또는 put_nowait()를 호출했을 때 발생하는 예외입니다.

기타

로깅

asyncio 패키지가 수행하는 모든 로깅은 단일 logging.Logger 객체인 asyncio.logger를 사용합니다. 로깅을 사용자 정의하려면 이 객체에 표준 Logger API를 사용하면 됩니다. (단, 객체 자체를 교체하지는 마십시오.)

UNIX에서의 SIGCHLD 처리

서브프로세스 프로토콜의 process_exited() 메서드를 효율적으로 구현하려면 SIGCHLD 시그널 핸들러가 필요합니다. 그러나 시그널 핸들러는 메인 스레드에 연결된 이벤트 루프에서만 설정할 수 있습니다. 다른 스레드에서 실행되는 이벤트 루프에서도 서브프로세스를 생성할 수 있도록, 여러 이벤트 루프가 하나의 SIGCHLD 핸들러를 공유할 수 있게 하는 메커니즘이 있습니다. asyncio.get_child_watcher()와 asyncio.set_child_watcher()라는 두 개의 추가 함수가 있으며, 이벤트 루프 정책에도 이에 대응하는 메서드가 있습니다.

자식 감시자 구현 클래스로는 FastChildWatcher와 SafeChildWatcher 두 가지가 있습니다. 둘 다 SIGCHLD를 사용합니다. 기본적으로는 SafeChildWatcher 클래스가 사용되며, 이 클래스는 많은 서브프로세스가 동시에 존재할 때 비효율적입니다. FastChildWatcher 클래스는 효율적이지만, asyncio 이벤트 루프를 사용하지 않고 서브프로세스를 생성하는 다른 코드(C 코드든 Python 코드든)와 간섭을 일으킬 수 있습니다. 서브프로세스를 생성하는 다른 코드를 사용하지 않는다고 확신한다면, 빠른 구현을 사용하기 위해 메인 스레드에서 다음을 실행하십시오:

watcher = asyncio.FastChildWatcher()
asyncio.set_child_watcher(watcher)

희망 목록

(이 기능들이 바람직하다는 데에는 의견이 일치하지만, Python 3.4 베타 1이 릴리스될 때까지 구현이 준비되지 않았으며, Python 3.4 릴리스 주기의 나머지 기간에 적용되는 기능 동결로 인해 이렇게 늦은 단계에서는 추가할 수 없습니다. 그러나 Python 3.5에서는 추가되기를 바라며, PyPI 배포판에는 그보다 더 일찍 추가될 수도 있습니다.)

  • TCP 소켓을 SSL/TLS로 업그레이드하는 “start TLS” 연산 지원.

이후 구현된 이전 희망 목록 항목(단, PEP에는 명시되지 않음):

  • UNIX 도메인 소켓.
  • 루프별 오류 처리 콜백.

미해결 문제

(이 문제들은 PEP가 수락되면서 사실상 현 상태를 유지하는 쪽으로 해결되었다는 점에 유의하십시오. 그러나 PEP가 잠정적 지위에 있으므로 Python 3.5에서 이 결정들을 수정할 수 있습니다.)

  • create_connection()과 create_datagram_endpoint()에는 proto 인자가 있는데 create_server()에는 없는 이유는 무엇입니까? 그리고 getaddrinfo()의 family, 플래그(flag), proto 인자는 왜 어떤 때는 0이고 어떤 때는 (값이 역시 0인) 이름 있는 상수입니까?
  • 루프가 중지되는 중인지 알려 주는 조회 메서드가 더 필요합니까?
  • Handle에 더 완전한 공개 API가 필요합니까? 사용 사례는 무엇입니까?
  • 디버깅 API가 필요합니까? 예를 들어 많은 내용을 로그로 남기거나, (큐가 비워지는 속도보다 빠르게 채워지는 것 같은) 비정상적인 상황을 로그로 남기거나, 심지어 너무 오래 걸리는 콜백까지 로그로 남기는 것 등입니다…
  • 인트로스펙션(introspection) API가 필요합니까? 예를 들어 파일 디스크립터가 주어졌을 때 읽기 콜백을 조회하는 기능입니다. 또는 다음 예약된 호출이 언제인지 조회하는 기능입니다. 또는 콜백이 등록된 파일 디스크립터의 목록을 조회하는 기능입니다. 현재는 이 모든 작업에 내부 구현을 사용해야 합니다.
  • sock_sendto()와 sock_recvfrom(), 그리고 어쩌면 pipe_read() 같은 다른 메서드 등 소켓 I/O 메서드가 더 필요합니까? 사용자가 직접 작성할 수 있을 것이라고 생각합니다(그리 어려운 일이 아닙니다).
  • 다양한 타임아웃을 제어하는 API가 필요할 수 있습니다. 예를 들어 DNS 조회, 연결, ssl/tls 핸드셰이크, 유휴 연결, close/shutdown에 소요되는 시간을 제한하고 싶을 수 있으며, 심지어 세션별로 제한하고 싶을 수도 있습니다. 일부 메서드에 timeout 키워드 인자를 추가하는 것으로 충분할 수도 있으며, 다른 타임아웃은 call_later()와 Task.cancel()을 영리하게 사용하여 구현할 수 있을 것입니다. 하지만 일부 연산에는 기본 타임아웃이 필요할 수 있으며, 특정 연산의 기본값을 전역적으로(즉, 이벤트 루프별로) 변경하고 싶을 수도 있습니다.

참고 문헌

감사의 글

PEP 3153 외에도 PEP 380과 Greg Ewing의 yield from 튜토리얼, Twisted, Tornado, ZeroMQ, pyftpdlib, 그리고 wattle(Steve Dower의 대안 제안)의 영향을 받았습니다. Google App Engine용 NDB 라이브러리에서 비동기 지원을 작업했던 이전 경험이 중요한 출발점이 되었습니다.

2012년 9월부터 12월까지 python-ideas에서 있었던 수많은 논의와 그 이후 python-tulip에서 이어진 더 많은 논의, Steve Dower 및 Dino Viehland와의 Skype 세션, Ben Darnell과 주고받은 이메일 및 그의 방문, Niels Provos(libevent의 원저자)와의 면담, 그리고 Glyph, Brian Warner, David Reid, Duncan McGreggor를 비롯한 여러 Twisted 개발자와의 대면 회의(및 잦은 이메일 교환)에 감사드립니다.

구현에 참여한 기여자로는 Eli Bendersky, Gustavo Carneiro (Gambit Research), Saúl Ibarra Corretgé, Geert Jansen, A. Jesse Jiryu Davis, Nikolay Kim, Charles-François Natali, Richard Oudkerk, Antoine Pitrou, Giampaolo Rodolá, Andrew Svetlov, 그리고 버그 보고나 수정 사항을 제출해 주신 많은 분이 있습니다.

공식 PEP BDFL로서 피드백을 주신 Antoine Pitrou 님께 감사드립니다.