web-dev-qa-db-ja.com

asyncioで1行ずつファイルを読み取る

いくつかのログファイルが書き込まれているときに読み取り、その入力をasyncioで処理したいと思います。コードはWindowsで実行する必要があります。 stackoverflowとWebの両方を検索して理解したことから、非同期ファイルI/Oはほとんどのオペレーティングシステムで注意が必要です(たとえば、selectは意図したとおりに動作しません)。他のメソッド(スレッドなど)でこれを行うことができると確信していますが、asyncioを試して、それがどのようなものかを確認します。最も役立つ答えは、おそらく、この問題の解決策の「アーキテクチャ」がどのように見えるべきか、つまり異なる機能やコルーチンがどのように呼び出されるか、またはスケジュールされるべきかを説明するものでしょう。

以下は、ファイルを1行ずつ読み取るジェネレーターを提供します(ポーリングを通じて、許容されます)。

import time

def line_reader(f):
    while True:
        line = f.readline()
        if not line:
            time.sleep(POLL_INTERVAL)
            continue
        process_line(line)

監視および処理するファイルがいくつかあるため、この種のコードにはスレッドが必要です。私はasyncioでより使いやすいように少し変更しました:

import asyncio

def line_reader(f):
    while True:
        line = f.readline()
        if not line:
            yield from asyncio.sleep(POLL_INTERVAL)
            continue
        process_line(line)

この種の機能は、asyncioイベントループを介してスケジュールすると機能しますが、process_dataブロック、それはもちろん良くありません。始めたとき、私は解決策が次のようになると想像しました

def process_data():
    ...
    while True:
        ...
        line = yield from line_reader()
        ...

しかし、私はその作業を行う方法を理解できませんでした(少なくともprocess_dataかなりの状態を管理します)。

この種のコードをどのように構成すべきかについてのアイデアはありますか?

17
josteinb

StackoverflowとWebの両方を検索して理解したことから、非同期ファイルI/Oはほとんどのオペレーティングシステムで扱いにくい(たとえば、selectは意図したとおりに機能しません)。他のメソッド(スレッドなど)でこれを行うことができると確信していますが、asyncioを試して、それがどのようなものかを確認します。

asyncioisselectは内部の* nixシステムに基づいているため、非実行はできません。 -スレッドを使用せずにファイルI/Oをブロックします。 Windowsでは、asyncio[〜#〜] iocp [〜#〜] を使用できます。これは非ブロッキングファイルI/Oをサポートしますが、これはasyncio

I/Oが遅い場合にイベントループをブロックしないように、スレッドでI/O呼び出しをブロックする必要があることを除いて、コードは問題ありません。幸いなことに、loop.run_in_executor関数を使用してスレッドへの作業をオフロードするのは本当に簡単です。

まず、I/O専用のスレッドプールをセットアップします。

from concurrent.futures import ThreadPoolExecutor
io_pool_exc = ThreadPoolExecutor()

そして、executorへのブロッキングI/O呼び出しをオフロードするだけです。

...
line = yield from loop.run_in_executor(io_pool_exc, f.readline)
...
15
Jashandeep Sohi

aiofiles を使用:

async with aiofiles.open('filename', mode='r') as f:
    async for line in f:
        print(line)

EDIT 1

@Jashandeepが言及したように、ブロック操作に注意する必要があります。

別の方法は、selectまたはepollです。

from select import select

files_to_read, files_to_write, exceptions = select([f1, f2], [f1, f2], [f1, f2], timeout=.1)

ここでは、timeoutパラメーターが重要です。

参照: https://docs.python.org/3/library/select.html#select.select

EDIT 2

loop.add_reader() を使用して、読み取り/書き込み用のファイルを登録できます。

ループ内で内部EPOLLハンドラーを使用します。

EDIT 3

ただし、通常のファイルではEpollは機能しないことに注意してください。

23
pylover

あなたのコード構造は私にとって見栄えがよく、次のコードは私のマシンで問題なく動作します:

import asyncio

PERIOD = 0.5

@asyncio.coroutine
def readline(f):
    while True:
        data = f.readline()
        if data:
            return data
        yield from asyncio.sleep(PERIOD)

@asyncio.coroutine
def test():
    with open('test.txt') as f:
        while True:
            line = yield from readline(f)
            print('Got: {!r}'.format(line))

loop = asyncio.get_event_loop()
loop.run_until_complete(test())
3
Vincent

asyncioはまだファイル操作をサポートしていません。申し訳ありません。

したがって、問題を解決することはできません。

0
Andrew Svetlov