Trio - Pemrograman Asinkron untuk Orang

gambar

Python memiliki pustaka Trio - pustaka pemrograman asinkron.
Mengenal Trio terutama akan menarik bagi mereka yang bekerja di Asyncio, karena itu adalah alternatif yang baik yang memungkinkan Anda untuk menyelesaikan beberapa masalah yang tidak dapat ditangani Asyncio. Dalam ulasan ini, kami akan mempertimbangkan apa itu Trio dan fitur apa yang diberikan kepada kami.

Bagi mereka yang baru mulai bekerja dalam pemrograman asinkron, saya mengusulkan untuk membaca pengantar kecil tentang apa itu asinkron dan sinkronisasi.

Sinkronisasi dan asinkron


Dalam pemrograman sinkron, semua operasi dilakukan secara berurutan, dan Anda dapat memulai tugas baru hanya setelah menyelesaikan yang sebelumnya. Namun, salah satu poin “sakitnya” adalah bahwa jika salah satu utas telah mengerjakan tugas untuk waktu yang sangat lama, seluruh program dapat membeku. Ada tugas yang tidak memerlukan daya komputasi, tetapi membutuhkan waktu prosesor, yang dapat digunakan lebih rasional dengan memberikan kontrol ke utas lainnya. Dalam pemrograman sinkron, tidak ada cara untuk menjeda tugas saat ini untuk menyelesaikan yang berikut di celahnya.

Untuk apa asinkron?? Penting untuk membedakan antara asinkron yang sebenarnya dan input-output asinkron. Dalam kasus kami, kami berbicara tentang input-output asinkron. Secara global - untuk menghemat waktu dan lebih efisien menggunakan fasilitas produksi. Asynchrony memungkinkan Anda untuk melewati area bermasalah pada utas. Dalam input-output asinkron, utas saat ini tidak akan menunggu eksekusi beberapa peristiwa eksternal, tetapi akan memberikan kontrol ke utas lainnya. Jadi, pada kenyataannya, hanya satu utas yang dijalankan sekaligus. Thread yang telah memberikan kontrol masuk ke antrian dan menunggu kontrol untuk kembali ke sana. Mungkin, pada saat itu, peristiwa eksternal yang diharapkan akan terjadi dan akan mungkin untuk terus bekerja. Ini akan memungkinkan Anda untuk beralih di antara tugas-tugas untuk meminimalkan pemborosan waktu.

Dan sekarang kita bisa kembali ke apa adanyaAsyncio . Pengoperasian loop peristiwa pustaka ini ( loop acara), yang mencakup antrian tugas dan loop itu sendiri. Siklus mengontrol pelaksanaan tugas, yaitu, ia mengambil tugas dari antrian dan menentukan apa yang akan terjadi padanya. Misalnya, mungkin menangani tugas I / O. Yaitu, loop peristiwa memilih tugas, mendaftar dan pada waktu yang tepat memulai prosesnya.

Coroutine adalah fungsi khusus yang mengembalikan kontrol tugas ini kembali ke loop acara, yaitu, mengembalikannya ke antrian. Penting bahwa coroutine ini diluncurkan secara tepat melalui serangkaian acara.

Juga ada masa depan- objek di mana hasil saat ini dari pelaksanaan tugas disimpan. Ini mungkin informasi bahwa tugas belum diproses atau hasilnya sudah diperoleh; atau mungkin ada pengecualian.

Secara umum, perpustakaan Asyncio terkenal, namun, memiliki sejumlah kekurangan yang dapat ditutup oleh Trio.

Trio


Menurut penulis perpustakaan, Nathaniel Smith , mengembangkan Trio, ia berusaha membuat alat yang ringan dan mudah digunakan untuk pengembang, yang akan memberikan input / output asinkron paling sederhana dan penanganan kesalahan.

Fitur penting dari Trio adalah manajemen konteks asinkron, yang tidak dimiliki Asyncio. Untuk melakukan ini, penulis membuat dalam Trio apa yang disebut "kamar anak"(pembibitan) - area pembatalan yang bertanggung jawab atas atomicity (kontinuitas) dari sekelompok utas. Gagasan utamanya adalah bahwa jika di “pembibitan” salah satu coroutine gagal, maka semua aliran di “pembibitan” akan berhasil diselesaikan atau dibatalkan. Bagaimanapun, hasilnya akan benar. Dan hanya ketika semua coroutine selesai, setelah keluar dari fungsi, pengembang sendiri memutuskan bagaimana untuk melanjutkan.

Artinya, "anak-anak" memungkinkan Anda untuk mencegah kelanjutan pemrosesan kesalahan, yang dapat mengarah pada fakta bahwa segala sesuatu akan "jatuh", atau hasilnya akan salah.
Inilah yang bisa terjadi dengan Asyncio, karena di Asyncio prosesnya tidak berhenti, meskipun ada kesalahan. Dan dalam hal ini, pertama, pengembang tidak akan tahu apa yang sebenarnya terjadi pada saat kesalahan, dan kedua, pemrosesan akan berlanjut.

Contohnya


Pertimbangkan contoh paling sederhana dari dua fitur yang bersaing:

Asyncio

import asyncio

async def foo1():
    print('  foo1: ')
    await asyncio.sleep(2)
    print('  foo1: ')

async def foo2():
    print('  foo2: ')
    await asyncio.sleep(1)
    print('  foo2: ')

loop = asyncio.get_event_loop()
bundle = asyncio.wait([
    loop.create_task(foo1()),
    loop.create_task(foo2()),
])
try:
    loop.run_until_complete(bundle)
finally:
    loop.close()

Trio

import trio

async def foo1():
    print('  foo1: ')
    await trio.sleep(2)
    print('  foo1: ')

async def foo2():
    print('  foo2: ')
    await trio.sleep(1)
    print('  foo2: ')

async def root():
    async with trio.open_nursery() as nursery:
        nursery.start_soon(foo1)
        nursery.start_soon(foo2)

trio.run(root)

dalam kedua kasus hasilnya akan sama:

foo1: 
foo2: 
foo2: 
foo1: 

Secara struktural, kode Asyncio dan Trio dalam contoh ini serupa.

Perbedaan yang jelas adalah bahwa Trio tidak memerlukan penyelesaian eksplisit dari loop acara.

Pertimbangkan contoh yang sedikit lebih hidup. Mari membuat panggilan ke layanan web untuk mendapatkan cap waktu.

Untuk Asyncio, kami juga akan menggunakan aiohttp :

import time
import asyncio
import aiohttp

URL = 'https://yandex.ru/time/sync.json?geo=213'
MAX_CLIENTS = 5

async def foo(session, i):
    start = time.time()
    async with session.get(URL) as response:
        content = await response.json()
        print(f'{i} | {content.get("time")} (  {time.time() - start})')

async def root():
    start = time.time()
    async with aiohttp.ClientSession() as session:
        tasks = [
            asyncio.ensure_future(foo(session, i))
            for i in range(MAX_CLIENTS)
        ]
        await asyncio.wait(tasks)
    print(f'  {time.time() - start}')

ioloop = asyncio.get_event_loop()
try:
    ioloop.run_until_complete(root())
finally:
    ioloop.close()

Untuk Trio kami menggunakan bertanya :

import trio
import time
import asks
URL = 'https://yandex.ru/time/sync.json?geo=213'
MAX_CLIENTS = 5

asks.init('trio')

async def foo(i):
    start = time.time()
    response = await asks.get(URL)
    content = response.json()
    print(f'{i} | {content.get("time")} (  {time.time() - start})')

async def root():
    start = time.time()
    async with trio.open_nursery() as nursery:
        for i in range(MAX_CLIENTS):
            nursery.start_soon(foo, i)

    print(f'  {time.time() - start}')

trio.run(root)

Dalam kedua kasus, kita mendapatkan sesuatu seperti

0 | 1543837647522 (  0.11855053901672363)
2 | 1543837647535 (  0.1389765739440918)
3 | 1543837647527 (  0.13904547691345215)
4 | 1543837647557 (  0.1591191291809082)
1 | 1543837647607 (  0.2100353240966797)
  0.2102828025817871

Baik. Bayangkan selama eksekusi salah satu coroutine, terjadi kesalahan
pada Asyncio.

async def foo(session, i):
    start = time.time()
    if i == 3:
        raise Exception
    async with session.get(URL) as response:
        content = await response.json()
        print(f'{i} | {content.get("time")} (  {time.time() - start})')

1 | 1543839060815 (  0.10857725143432617)
2 | 1543839060844 (  0.10372781753540039)
5 | 1543839060843 (  0.10734415054321289)
4 | 1543839060874 (  0.13985681533813477)
  0.15044045448303223
Traceback (most recent call last):
  File "...py", line 12, in foo
    raise Exception
Exception

untuk trio

async def foo(i):
    start = time.time()
    response = await asks.get(URL)
    content = response.json()
    if i == 3:
        raise Exception
    print(f'{i} | {content.get("time")} (  {time.time() - start})')


4 | 1543839223372 (  0.13524699211120605)
2 | 1543839223379 (  0.13848185539245605)
Traceback (most recent call last):
  File "...py", line 28, in <module>
    trio.run(root)
  File "/lib64/python3.6/site-packages/trio/_core/_run.py", line 1337, in run
    raise runner.main_task_outcome.error
  File "...py", line 23, in root
    nursery.start_soon(foo, i)
  File "/lib64/python3.6/site-packages/trio/_core/_run.py", line 397, in __aexit__
    raise combined_error_from_nursery
  File "...py", line 15, in foo
    raise Exception
Exception

Jelas terlihat bahwa di Trio, segera setelah terjadinya kesalahan, "membatalkan area" bekerja, dan dua dari empat tugas yang tidak mengandung kesalahan diakhiri secara tidak normal.

Di Asyncio, semua tugas sudah selesai, dan baru saat itulah trackback muncul.

Dalam contoh yang diberikan, ini tidak penting, tetapi mari kita bayangkan bahwa tugas-tugas dalam satu atau lain cara bergantung satu sama lain, dan serangkaian tugas harus memiliki sifat atomisitas. Dalam hal ini, respons yang tepat waktu terhadap kesalahan menjadi jauh lebih penting. Tentu saja, Anda dapat menggunakan menunggu asyncio.wait (tugas, return_when = FIRST_EXCEPTION) , tetapi Anda harus ingat untuk menyelesaikan tugas terbuka dengan benar.

Berikut adalah contoh lain:

Misalkan coroutine secara bersamaan mengakses beberapa layanan web yang serupa, dan respons pertama yang diterima adalah penting.

import asyncio
from asyncio import FIRST_COMPLETED
import aiohttp

URL = 'https://yandex.ru/time/sync.json?geo=213'
MAX_CLIENTS = 5

async def foo(session):
    async with session.get(URL) as response:
        content = await response.json()
        return content.get("time")

async def root():
    async with aiohttp.ClientSession() as session:
        tasks = [
            asyncio.ensure_future(foo(session))
            for i in range(1, MAX_CLIENTS + 1)
        ]
        done, pending = await asyncio.wait(tasks, return_when=FIRST_COMPLETED)
        print(done.pop().result())
        for future in pending:
            future.cancel()

ioloop = asyncio.get_event_loop()
try:
    ioloop.run_until_complete(root())
except:
    ioloop.close()

Semuanya sangat sederhana. Satu-satunya persyaratan adalah mengingat untuk menyelesaikan tugas yang belum selesai.

Di Trio, melakukan manuver serupa sedikit lebih sulit, tetapi hampir tidak mungkin untuk meninggalkan "ekor" tidak terlihat segera:

import trio
import asks
URL = 'https://yandex.ru/time/sync.json?geo=213'
MAX_CLIENTS = 5
asks.init('trio')

async def foo(session, send_channel, nursery):
    response = await session.request('GET', url=URL)
    content = response.json()
    async with send_channel:
        send_channel.send_nowait(content.get("time"))
    nursery.cancel_scope.cancel()

async def root():
    send_channel, receive_channel = trio.open_memory_channel(1)
    async with send_channel, receive_channel:
        async with trio.open_nursery() as nursery:
            async with asks.Session() as session:
                for i in range(MAX_CLIENTS):
                    nursery.start_soon(foo, session, send_channel.clone(), nursery)

        async with receive_channel:
            x = await receive_channel.receive()
            print(x)

trio.run(root)

nursery.cancel_scope.cancel () - coroutine pertama yang selesai akan memanggil fungsi di area undo yang akan membatalkan semua tugas lain, sehingga tidak perlu khawatir tentang hal itu secara terpisah.
Benar, untuk mentransfer hasil eksekusi coroutine ke fungsi yang menyebabkannya, Anda harus memulai saluran komunikasi.

Semoga ulasan perbandingan ini memberikan pemahaman tentang fitur utama Trio. Terimakasih untuk semua!

All Articles