web-dev-qa-db-fra.com

Lire le fichier ligne par ligne avec asyncio

Je souhaite lire plusieurs fichiers journaux au fur et à mesure qu'ils sont écrits et traiter leur entrée avec asyncio. Le code devra s'exécuter sur Windows. D'après ce que je comprends de la recherche à la fois sur stackoverflow et sur le Web, les E/S de fichiers asynchrones sont difficiles sur la plupart des systèmes d'exploitation (select ne fonctionnera pas comme prévu, par exemple). Bien que je sois sûr que je pourrais le faire avec d'autres méthodes (par exemple des threads), je pensais que j'essaierais asyncio pour voir à quoi cela ressemble. La réponse la plus utile serait probablement celle qui décrit à quoi devrait ressembler "l'architecture" d'une solution à ce problème, c'est-à-dire comment différentes fonctions et coroutines doivent être appelées ou planifiées.

Ce qui suit me donne un générateur qui lit les fichiers ligne par ligne (par interrogation, ce qui est acceptable):

import time

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

Avec plusieurs fichiers à surveiller et à traiter, ce type de code nécessiterait des threads. Je l'ai légèrement modifié pour être plus utilisable avec 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)

Ce genre de travail lorsque je le planifie via la boucle d'événement asyncio, mais si process_data blocs, alors ce n'est bien sûr pas bon. Au début, j'ai imaginé que la solution ressemblerait à quelque chose comme

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

mais je ne pouvais pas comprendre comment faire ce travail (du moins pas sans process_data gérer un peu d'état).

Des idées sur la façon de structurer ce type de code?

17
josteinb

D'après ce que je comprends de la recherche à la fois sur stackoverflow et sur le Web, les E/S de fichiers asynchrones sont difficiles sur la plupart des systèmes d'exploitation (select ne fonctionnera pas comme prévu, par exemple). Bien que je sois sûr que je pourrais le faire avec d'autres méthodes (par exemple des threads), je pensais que j'essaierais asyncio pour voir à quoi cela ressemble.

asyncio est select basé sur des systèmes * nix sous le capot, vous ne pourrez donc pas faire non -blocage des E/S de fichiers sans utiliser de threads. Sous Windows, asyncio peut utiliser IOCP , qui prend en charge les E/S de fichiers non bloquants, mais cela n'est pas pris en charge par asyncio.

Votre code est correct, sauf que vous devez bloquer les appels d'E/S dans les threads, afin de ne pas bloquer la boucle d'événements si les E/S sont lentes. Heureusement, il est très simple de décharger le travail sur les threads en utilisant le loop.run_in_executor une fonction.

Tout d'abord, configurez un pool de threads dédié pour vos E/S:

from concurrent.futures import ThreadPoolExecutor
io_pool_exc = ThreadPoolExecutor()

Ensuite, déchargez simplement les appels d'E/S bloquants vers l'exécuteur:

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

Utilisation des aiofiles :

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

EDIT 1

Comme l'a mentionné @Jashandeep, vous devez vous soucier des opérations de blocage:

Une autre méthode est select et ou epoll:

from select import select

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

Le paramètre timeout est important ici.

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

EDIT 2

Vous pouvez enregistrer un fichier en lecture/écriture avec: loop.add_reader ()

Il utilise le gestionnaire EPOLL interne à l'intérieur de la boucle.

EDIT 3

Mais rappelez-vous que l'Epoll ne fonctionnera pas avec des fichiers normaux.

23
pylover

Votre structure de code me semble bonne, le code suivant fonctionne bien sur ma machine:

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 ne prend pas encore en charge les opérations sur les fichiers, désolé.

Ainsi, il ne peut pas résoudre votre problème.

0
Andrew Svetlov