非同期でPythonでの非同期プログラミング


JavaScriptの非同期プログラミングから来ている人々は何も新しいものではありませんが、Python開発者が非同期関数と将来に使用されているために

並行性対並列性


並行性と並列性は本当に似ているが、プログラミングでは重要な違いがある.
あなたがあなたの本を書いている沸騰するのを待つ間、あなたがあなたの文書を一時停止している間、あなたがあなたの本を書いている間、あなたが2つの仕事の間で切替えている間、料理をしている間、あなたが現在の両方の仕事をしているようであるとしても、料理をしている間、本を書いています.これは同時実行と呼ばれます.並列にこれらの2つのタスクを行う唯一の方法は、2つの人々、1つの書き込みと1つの料理、マルチコアCPUが行うことです.

なぜasyncio


Asyncプログラミングでは、単一のスレッドで実行される同時実行コードを記述できます.複数のスレッドに比べて最初の利点は、スケジューラが1つのタスクから別のタスクに切り替えることを決定するということです.
def queue_push_back(x):
    if len(list) < max_size:
        list.append(x)
マルチスレッドプログラムで上記のコードを実行すると、2つのスレッドが同時に行2を実行することが可能です.したがって、2つの項目が同時にキューに追加され、潜在的にキューサイズをmax_sizeAsyncプログラミングの別の利点は、メモリ使用量です.新しいスレッドが作成されるたびに、いくつかのメモリがコンテキスト切り替えを許可するために使用されます.ASCRCプログラミングを使用する場合、これは単一のスレッドで実行されるため、問題ではありません.

AsyncコードをPythonで書く方法


asyncioは3つの主要なコンポーネントを持っています

コルーチン


coroutineはキーワードを使って宣言できる非同期関数の結果ですasync 以前def
async def my_task(args):
    pass

my_coroutine = my_task(args)
を使用して関数を宣言するとasync キーワードが実行されていない場合、代わりにCoroutineオブジェクトが返されます.
コルーチンからAsync関数の出力を読むには2通りあります.
最初の方法はawait キーワード、これはasync関数の中でのみ可能であり、コルーチンが終了して結果を返すのを待ちます
result = await my_task(args)
次のセクションで見るように、2番目の方法はイベントループに追加することです.

イベントループ


イベントループは、非同期コードを実行し、async関数の切り替え方法を決定するオブジェクトです.イベントループを作成した後に、複数のcoroutinesを追加することができますrun_until_complete or run_forever が呼ばれる.
# create loop
loop = asyncio.new_event_loop()
# add coroutine to the loop
future = loop.create_task(my_coroutine)
# stop the program and execute all coroutine added
# to the loop concurrently
loop.run_until_complete(future)
loop.close()

未来


将来は非同期関数の出力のプレースホルダとして機能するオブジェクトです.
イベントループにcorutineを追加すると、将来が作成されます.これには二つの方法があります.
future1 = loop.create_task(my_coroutine)
# or
future2 = asyncio.ensure_future(my_coroutine)
最初のメソッドはcoroutineをループに追加し、task これは将来のサブタイプです.第2の方法は非常に似ています、それはコルーチンをとって、それをデフォルトループに加えます、唯一の違いはそれが将来を受け入れることができるということです.

単純なプログラム


import asyncio

async def my_task(args):
    pass

def main():
    loop = asyncio.new_event_loop()
    coroutine1 = my_task()
    coroutine2 = my_task()
    task1 = loop.create_task(coroutine1)
    task2 = loop.create_task(coroutine2)
    loop.run_until_complete(asyncio.wait([task1, task2]))
    print('task1 result:', task1.result())
    print('task2 result:', task2.result())
    loop.close()
非同期関数を実行するには、まずコルーチンを作成する必要があります.ここまで私たちのasync関数内のコードは実行されませんloop.run_until_completed イベントループは、ループに追加されたすべてのコルーチンを実行し始めるloop.create_task or asyncio.ensure_future . loop.run_until_completed あなたが引数として与えた未来が完了するまでプログラムをブロックします.使用例ではasyncio.wait() すべての先物が引数リストに渡されたときのみ完了する未来を作成します.

async関数


Pythonで非同期関数を書いている間、気をつけておくべきことは、あなたが使ったからですasync 以前def それはあなたの関数が同時に実行されることを意味しません.あなたが通常の機能をとって、加えるならばasync ループの前に別のコルーチンを実行する機能を中断することが許可されていないので、イベントループは、中断せずに機能を実行します.イベントループがCoooutineを変更することができる場所を指定するのは本当に簡単です、あなたがキーワードを使用するたびに、あなたの機能を実行して、ループに登録されるもう一つのコルーチンを走らせるのを止めることができるイベントループを待ちます.
async def print_numbers_async1(n, prefix):
    for i in range(n):
        print(prefix, i)

async def print_numbers_async2(n, prefix):
    for i in range(n):
        print(prefix, i)
        if i % 5 == 0:
            await asyncio.sleep(0)

loop1 = asyncio.new_event_loop()
count1_1 = loop1.create_task(print_numbers_async1(10, 'c1_1')
count2_1 = loop1.create_task(print_numbers_async1(10, 'c2_1')
loop1.run_until_complete(asyncio.wait([count1_1, count2_1])
loop1.close()

loop2 = asyncio.new_event_loop()
count1_2 = loop1.create_task(print_numbers_async1(10, 'c1_2')
count2_2 = loop1.create_task(print_numbers_async1(10, 'c2_2')
loop2.run_until_complete(asyncio.wait([count1_2, count2_2])
loop2.close()
このコードを実行するならば、ループ1が最初にすべての数字を接頭辞で印刷することを見るでしょうc1_1 それから、接頭辞c2_1 番目のループごとに5つの数字のループは、タスクを変更します.

実世界例


Pythonで非同期プログラミングの基礎を知っているので、インターネットからページのリストをダウンロードし、ページの最初の3行を含むプレビューを印刷する、より現実的なコードを書きましょう.
import aiohttp
import asyncio

async def print_preview(url):
    # connect to the server
    async with aiohttp.ClientSession() as session:
        # create get request
        async with session.get(url) as response:
            # wait for response
            response = await response.text()

            # print first 3 not empty lines
            count = 0
            lines = list(filter(lambda x: len(x) > 0, response.split('\n')))
            print('-'*80)
            for line in lines[:3]:
                print(line)
            print()

def print_all_pages():
    pages = [
        'http://textfiles.com/adventure/amforever.txt',
        'http://textfiles.com/adventure/ballyhoo.txt',
        'http://textfiles.com/adventure/bardstale.txt',
    ]

    tasks =  []
    loop = asyncio.new_event_loop()
    for page in pages:
        tasks.append(loop.create_task(print_preview(page)))

    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()
このコードはかなり簡単に理解する必要があります.最初の3行の空の行を表示する非同期関数を作成することで始めます.次に、ページコールのリスト内の各ページの関数を作成しますprint_preview , ループにcoroutineを追加し、将来のタスクのリスト内に格納します.最後に、我々はそれに追加されたcoroutineを実行するイベントループを実行し、それはすべてのページのプレビューを印刷します.

非同期発電機


私が話したい最後の機能は非同期発電機です.非同期発電機の実装は非常に簡単です.
import asyncio
import math
import random

async def is_prime(n):
    if n < 2:
        return True
    for i in range(2, n):
        # allow event_loop to run other coroutine
        await asyncio.sleep(0)
        if n % i == 0:
            return False
    return True

async def prime_generator(n_prime):
    counter = 0
    n = 0
    while counter < n_prime:
        n += 1
        # wait for is_prime to finish
        prime = await is_prime(n)
        if prime:
            yield n
            counter += 1

async def check_email(limit):
    for i in range(limit):
        if random.random() > 0.8:
            print('1 new email')
        else:
            print('0 new email')
        await asyncio.sleep(2)

async def print_prime(n):
    async for prime in prime_generator(n):
        print('new prime number found:', prime)

def main():
    loop = asyncio.new_event_loop()
    prime = loop.create_task(print_prime(3000))
    email = loop.create_task(check_email(10))
    loop.run_until_complete(asyncio.wait([prime, email]))
    loop.close()

例外処理


未処理の例外がコルーチンの内部で発生した場合、それは通常の同期プログラミングのようにプログラムを中断しません.代わりに、将来的に格納され、プログラム終了時に例外を処理しない場合は、次のエラーが発生します
Task exception was never retrieved
これを修正するには2つの方法があり、将来の結果にアクセスしたり、将来の例外メソッドを呼び出すときに例外をキャッチします.
try:
    # this will raise the exception raised during the coroutine execution
    my_promise.result()
catch Exception:
    pass

# this will return the exception raised during the coroutine execution
my_promise.exception()

より深い


この点まですべてを読んだ場合は、同時実行するコードを書くためにasyncioを使う方法を知っていなければなりません.しかし、もっと深く行きたいなら、asyncioがどのように動作するかを理解したいなら、以下のビデオを見てください
場合は、任意の質問をコメントを残していると私はできるだけ早くあなたにリプレイされます