FIREして市場に落ちている金を拾うブログ

30代でFIREし、プログラムを勉強して米国株式と仮想通貨で落ちている金をせっせと拾う記録。

Websocket実装に向けて非同期処理を勉強する(REST API)

REST APIだけで実装しているbotにWebsocket入れたーい。
そんな思いでまちゅけんさんの記事を勉強したメモです。元記事はこちら。

zenn.dev

import asyncio
import aiohttp

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.bybit.com/v2/public/tickers?symbol=BTCUSD") as resp:
            data = await resp.text()
        print(data)

asyncio.run(main())

5行目で非同期コンテキストマネージャーを用いて HTTPクライアントセッション [3] を作成しています

いきなり用語がわからない。非同期コンテキストマネージャーとはなんぞや?
調べてみると、withがコンテキストマネージャというらしい。それをasyncで使ってるので非同期コンテキストマネージャ。
調べてないが、ClientSessionをwithで開くと、with構文終了後、セッションが勝手に閉じるようになってると思われる。

6行目でまた非同期コンテキストマネージャーを用いて HTTPリクエスト(GET)を行いレスポンスを待機 ( HTTP通信 [4] )します ( HTTPヘッダ が取得されます)

書いてあることはわかるが、必要性が理解できない。
5行目で非同期コンテキストマネージャー使ってるので、セッションは閉じるようになってるはず。6行目でも非同期コンテキストマネージャーを使う必要があるのか?なにか閉じるものがあるのか?
7行目のawaitとはどう使い分けているのか?根底コネクタってなんだ?

なお、コルーチン関数で実装されているメソッドはawait文で実行する必要があるらしい。へー。

そう考えていると、次にもう一つ違う書き方が。

import asyncio
import aiohttp

async def main():
    async with aiohttp.ClientSession() as session:
        r = await session.get("https://api.bybit.com/v2/public/tickers", params={"symbol": "BTCUSD"})
        data = await r.json()
        print(data["result"])
 
asyncio.run(main())

やっぱasync withでgetする必要はないらしい。だよねー。

import asyncio
import aiohttp
import time

async def fetch(url, params={}):
    r = await session.get(url, params=params)
    data = await r.json()
    return data

async def main():
    global session
    async with aiohttp.ClientSession() as session:
        stime = time.time()

        results = []
        coro1 = fetch("https://api.bybit.com/v2/public/tickers", params={"symbol": "BTCUSD"})
        coro2 = fetch("https://api.bybit.com/v2/public/tickers", params={"symbol": "ETHUSD"})
        coro3 = fetch("https://api.bybit.com/v2/public/tickers", params={"symbol": "EOSUSD"})
        coro4 = fetch("https://api.bybit.com/v2/public/tickers", params={"symbol": "XRPUSD"})
        task1 = asyncio.create_task(coro1)
        task2 = asyncio.create_task(coro2)
        task3 = asyncio.create_task(coro3)
        task4 = asyncio.create_task(coro4)
        await task1
        await task2
        await task3
        await task4
        results.append(task1.result())
        results.append(task2.result())
        results.append(task3.result())
        results.append(task4.result())
        print(results)

        etime = time.time()
        print(f"Total {etime - stime:.4f} sec")

asyncio.run(main())

前節との最も重要な違いは20-23行目でコルーチンをTask化し実行を イベントループ にスケジュールし、それを24-27行目で各Taskが終了するのを await文 で待機しているところです。
前節ではイベントループにスケジュールせずに直接await文で実行していました。ここで分かるのは await文は "実行する" というよりも、awaitを行った行で制御を離してイベントループに任せて "awaitしたオブジェクトが終了するまで待機する" という命令 であることです。

すごくよくわかった。
非同期処理の本質的なメリットは、1つ目のリクエストへのレスポンスが返ってくる前に、2つ目のリクエストを投げられること。
そのため重要なのは、イベントループにタスクを入れきったあと、awaitで終了するまで待機すること!

Websocketは記事を分けます。