Estudando as possibilidades do MicroPython para seus propósitos, deparei-me com uma das implementações da biblioteca assíncrona e, após uma breve correspondência com Piter Hinch , o autor da biblioteca, percebi que precisava entender mais profundamente os princípios, conceitos básicos e erros típicos do uso de métodos de programação assíncronos. Além disso, a seção para iniciantes é apenas para mim.Este guia é destinado a usuários com diferentes níveis de experiência com
assíncio , incluindo uma seção especial para iniciantes.
Conteúdo0. Introdução0.1 .___ Instalando o
uasyncio em um dispositivo vazio (hardware)
1. Planejando a execução conjunta do programa1.1 .___ Modules
2. biblioteca uasyncio2.1 .___ Estrutura do programa: ciclo de processamento de eventos
2.2 .___ Corotinas
2.2.1
.______ Filas
rotativas para participar do planejamento2.2.2 .______
Iniciando um retorno de chamada de função ( retorno de chamada )2.2.3 .______
Notas: corotinas como métodos relacionados. Os valores retornados.2.3 .___ Atrasos
3. Sincronização e suas classes3.1 .___ Bloquear
Bloquear3.1.1 .______
Bloqueios e tempos limite3.2 .___
Evento3.2.1 .______
Valor do evento
3.3 .___ Barreira
Barreira3.4 .___
Semáforo3.4.1 .______
Semáforo limitado3.5 .___ Queue
Queue3.6 .___ Outras classes de sincronização
4. Desenvolvimento de classe para assíncio4.1 .___ Classes usando
aguardar4.1.1 .______
Uso em gerenciadores de contexto4.1.2
.______ Aguardar na corotina4.2 .___ Iteradores assíncronos
4.3 .___ Gerenciadores de contexto assíncrono
5. Exceções a tempos limite e devido a cancelamentos de tarefas5.1 .___ Exceções
5.2 .___ Exceções devido a tempos limite e devido ao cancelamento de tarefas
5.2.1 .______
Cancelar tarefas5.2.2 .______
Corotinas com tempo limite6. Interação com dispositivos de hardware6.1 .___ Problemas de sincronização
6.2 .___ Dispositivos de votação com corotinas
6.3 .___ Usando o mecanismo de streaming
6.3.1 .______
Exemplo de driver UART6.4 .___ Desenvolvimento de driver para um dispositivo de streaming
6.5 .___ Exemplo completo:
aremote.py Driver para receptor de controle remoto IR.
6.6 .___ Driver para sensor de temperatura e umidade HTU21D.
7. Dicas e truques7.1 .___ O programa congela
7.2 .___
uasyncio salva estado
7.3 .___ Coleta de lixo
7.4 .___ Testing
7.5 .___ Erro comum. Pode ser difícil de encontrar.
7.6 .___ Programação usando soquetes (
soquetes )
7.6.1 .______
Problemas de WiFi7.7 .___ Argumentos do construtor de loop de eventos
8. Notas para iniciantes8.1 .___ Problema 1: loops de eventos
8.2 .___ Problema 2: métodos de bloqueio
8.3 .___ A abordagem
uasyncio8.4 .___ Planejamento no
uasyncio8.5 .___ Por que agendamento colaborativo, não baseado em
encadeamento (
_thread )?
8.6 .___ Interação
8.7 .___
Pesquisa0. IntroduçãoA maior parte deste documento assume alguma familiaridade com a programação assíncrona. Para iniciantes, uma introdução pode ser encontrada na seção 7.
A biblioteca
uasyncio para
MicroPython inclui um subconjunto da biblioteca
Python assíncrona e deve ser usada em microcontroladores. Como tal, ele ocupa uma pequena quantidade de RAM e está configurado para alternar rapidamente contextos com zero de alocação de RAM.
Este documento descreve o uso do
uasyncio com ênfase na criação de drivers para dispositivos de hardware.
O objetivo é projetar os drivers para que o aplicativo continue funcionando enquanto o driver aguarda uma resposta do dispositivo. Ao mesmo tempo, o aplicativo permanece sensível a outros eventos e interação do usuário.
Outra área importante de aplicação do
assíncio é a programação em rede: na Internet, você encontra informações suficientes sobre esse tópico.
Observe que o
MicroPython é baseado no
Python 3.4 com os complementos mínimos do
Python 3.5 . Exceto conforme detalhado abaixo, não há suporte para funções de versões
assíncronas anteriores a 3,4. Este documento define os recursos suportados neste subconjunto.
O objetivo deste guia é apresentar um estilo de programação compatível com o
CPython V3.5 e superior.
0.1 Instalar o uasyncio em um dispositivo vazio (hardware)É recomendável usar o firmware
MicroPython V1.11 ou posterior. Em muitas plataformas, a instalação não é necessária, pois o
uasyncio® já
está compilado na montagem. Para verificar, basta digitar REPL
import uasyncio
As instruções a seguir abrangem casos em que os módulos não estão pré-instalados. As
filas e os módulos
sincronizados são opcionais, mas são necessários para executar os exemplos fornecidos aqui.
Dispositivo conectado à InternetEm um dispositivo conectado à Internet e executando o firmware V1.11 ou posterior, é possível instalar usando a versão
upip integrada . Verifique se o dispositivo está conectado à sua rede:
import upip upip.install ( 'micropython-uasyncio' ) upip.install ( 'micropython-uasyncio.synchro' ) upip.install ( 'micropython-uasyncio.queues' )
As mensagens de erro do
upip não
são muito úteis. Se você receber um erro incompreensível, verifique a conexão com a Internet novamente.
Hardware sem conexão à Internet ( micropip )Se o dispositivo não tiver uma conexão com a Internet (por exemplo,
Pyboard V1.x ), a maneira mais fácil é iniciar a instalação do
micropip.py no computador para o diretório de sua escolha e copiar a estrutura de diretórios resultante para o dispositivo de destino. O utilitário
micropip.py é executado no
Python 3.2 ou posterior e no Linux, Windows e OSX. Mais informações podem ser encontradas
aqui .
Chamada típica:
$ micropip.py install -p ~/rats micropython-uasyncio $ micropip.py install -p ~/rats micropython-uasyncio.synchro $ micropip.py install -p ~/rats micropython-uasyncio.queues
Um dispositivo sem conexão à Internet (fonte de cópia)Se você não usar o
micropip.py , os arquivos deverão ser copiados da fonte. As instruções a seguir descrevem como copiar o número mínimo de arquivos no dispositivo de destino, bem como o caso em que o
uasyncio precisa ser compactado em um assembly compilado na forma de bytecode para reduzir o espaço ocupado. Para a versão mais recente compatível com o firmware oficial, os arquivos devem ser copiados do site oficial do
micropython-lib .
Clone a biblioteca no computador com o comando
$ git clone https://github.com/micropython/micropython-lib.git
No dispositivo de destino, crie o diretório
uasyncio (opcional no diretório lib) e copie os seguintes arquivos nele:
• uasyncio / uasyncio / __ init__.py
• uasyncio.core / uasyncio / core.py
• uasyncio.synchro / uasyncio / synchro.py
• uasyncio.queues / uasyncio / queues.py
Esses módulos
uasyncio podem ser compactados no bytecode colocando o diretório
uasyncio e seu conteúdo na porta do diretório
modules e recompilando o conteúdo.
1. Planejamento conjuntoA técnica de execução conjunta de várias tarefas é amplamente usada em sistemas embarcados, que oferece menos sobrecarga do que o agendamento de threading (
_thread ), evitando muitas armadilhas associadas a encadeamentos verdadeiramente assíncronos.
1.1 MódulosA seguir, é apresentada uma lista de módulos que podem ser executados no dispositivo de destino.
Bibliotecas1.
asyn.py Fornece
Bloqueio, Evento, Barreira, Semáforo, BoundedSemaphore, Condição, coletar primitivas de sincronização. Fornece suporte para o cancelamento de tarefas por meio das classes
NamedTask e
Cancellable .
2.
aswitch.py Representa classes para emparelhar interruptores e botões, bem como um objeto de programa com a possibilidade de atraso repetido. Os botões são uma generalização de switches que fornecem um estado lógico e não físico, além de eventos acionados por um toque duplo e longo.
Programas de demonstraçãoOs dois primeiros são mais úteis, pois fornecem resultados visíveis ao acessar o
hardware do
Pyboard .
- aledflash.py Pisca quatro indicadores do Pyboard de forma assíncrona por 10 segundos. A demonstração mais simples de uasyncio . Importe-o para executar.
- apoll.py Driver de dispositivo para o acelerômetro Pyboard . Demonstra o uso de corotinas para consultar um dispositivo. Trabalha por 20 s. Importe-o para executar. Requer Pyboard V1.x.
- astests.py Programas de teste / demonstração para o módulo aswitch .
- asyn_demos.py Demonstrações simples de cancelamento de tarefas.
- roundrobin.py Demonstração de planejamento circular. Também a referência para o planejamento de desempenho.
- waititable.py Demonstração de uma classe com uma espera. Uma maneira de implementar um driver de dispositivo que pesquisa uma interface.
- chain.py Copiado da documentação do Python . Demonstração da cadeia de corotina.
- aqtest.py Demonstração da classe Queue da biblioteca uasyncio .
- aremote.py Exemplo de driver de dispositivo para o protocolo NEC IR.
- auart.py Demonstração do fluxo de entrada e saída através do Pyboard UART .
- auart_hd.py Usando o Pyboard UART para se comunicar com um dispositivo usando o protocolo half-duplex. Adequado para dispositivos, por exemplo, usando o conjunto de comandos do modem AT.
- iorw.py Demonstração de um dispositivo leitor / gravador usando E / S de streaming.
Programas de teste- asyntest.py Testa classes de sincronização em asyn.py.
- testes de cancelamento de trabalho.
Utilitário1.
check_async_code.py O utilitário foi escrito em
Python3 para detectar erros de codificação específicos que podem ser difíceis de encontrar. Veja a seção 7.5.
ControlarO diretório
benchmarks contém scripts para verificar e caracterizar o
planejador uasyncio .
2. biblioteca uasyncioO conceito de
assíncio baseia-se na organização do planejamento para a execução conjunta de várias tarefas, que neste documento são denominadas
corotinas .
2.1 Estrutura do programa: loop de eventosConsidere o seguinte exemplo:
import uasyncio as asyncio async def bar (): count = 0, while True : count + = 1 print ( count ) await asyncio.sleep ( 1 )
A execução do programa continua até que
loop.run_forever seja chamado . Neste ponto, a execução é controlada pelo planejador. A linha após
loop.run_forever nunca será executada. O planejador executa o código de
barras porque foi colocado na fila no
planejador loop.create_task . Neste exemplo trivial, há apenas uma
barra de rotina. Se houvesse outros, o agendador os executaria durante os períodos em que a
barra estivesse em pausa.
A maioria dos aplicativos incorporados possui um loop de eventos contínuo. Um loop de eventos também pode ser iniciado de uma maneira que permita a conclusão usando o método do loop de eventos
run_until_complete ; É usado principalmente em testes. Exemplos podem ser encontrados no módulo
astests.py .
Uma instância de loop de eventos é um único objeto criado pela primeira chamada para
asyncio.get_event_loop () com dois argumentos inteiros opcionais que indicam o número de corotinas nas duas filas - o início e a espera. Normalmente, os dois argumentos terão o mesmo valor, igual a pelo menos o número de corotinas executadas simultaneamente no aplicativo. Geralmente, o valor padrão de 16 é suficiente.Se valores não padrão forem usados, consulte Argumentos do construtor de loop de eventos (seção 7.7.)
Se a corotina precisar chamar o método do loop de eventos (geralmente
create_task ), chamar
asyncio.get_event_loop () (sem argumentos) retornará efetivamente.
2.2 CorotinasUma rotina é criada da seguinte maneira:
async def foo ( delay_secs ): await asyncio.sleep ( delay_secs ) print ( 'Hello' )
Uma corotina pode permitir que outras corotinas sejam lançadas usando a instrução de
espera . Uma corotina deve conter pelo menos uma instrução de
espera . Isso faz com que a corotina seja executada antes da conclusão, antes da execução prosseguir para a próxima instrução. Considere um exemplo:
await asyncio.sleep ( delay_secs ) await asyncio.sleep ( 0 )
A primeira linha faz com que o código seja pausado por um tempo de atraso, enquanto outras corotinas usam esse tempo para sua execução. Um atraso de 0 faz com que todas as rotinas em execução sejam executadas em uma ordem cíclica até a próxima linha ser executada. Veja o exemplo de
roundrobin.py .
2.2.1 Fila para planejar uma rotina- EventLoop.create_task Argumento: Coroutine para executar. O planejador enfileira a corotina para iniciar o mais rápido possível. A chamada create_task retorna imediatamente. A corotina no argumento é especificada usando a sintaxe da chamada de função com os argumentos necessários.
- EventLoop.run_until_complete Argumento: Coroutine para executar. O planejador enfileira a corotina para iniciar o mais rápido possível. A corotina no argumento é especificada usando a sintaxe da chamada de função com os argumentos necessários. A chamada un_until_complete retorna quando a corotina é concluída: esse método fornece uma maneira de sair do planejador.
- aguardar Argumento: Uma rotina a ser executada, especificada usando a sintaxe da chamada de função. Inicia uma corrotina o mais rápido possível. A corotina pendente é bloqueada até que uma das corotinas esperadas seja concluída.
O acima é compatível com
CPython . Métodos adicionais de
uasyncio são discutidos nas Notas (Seção 2.2.3.).
2.2.2 Iniciando a função de retorno de chamadaOs retornos de chamada devem ser funções
Python projetadas para serem executadas em um curto período de tempo. Isso se deve ao fato de as corotinas não conseguirem trabalhar durante toda a duração da execução dessa função.
Os seguintes
métodos da classe
EventLoop usam retornos de chamada:
- call_soon - ligue o mais rápido possível. Args: retorno de chamada retorno de chamada a executar, * args quaisquer argumentos posicionais podem ser seguidos por vírgula.
- call_later - chama após um atraso em segundos. Args: atraso, retorno de chamada, * args
- call_later_ms - liga após um atraso em ms. Args: atraso, retorno de chamada, * args .
loop = asyncio.get_event_loop () loop.call_soon ( foo , 5 )
2.2.3 NotasUma corotina pode conter uma declaração de
retorno com valores de retorno arbitrários. Para obter esse valor:
result = await my_coro ()
Uma corotina pode ser limitada por métodos e deve conter pelo menos uma instrução de
espera .
2.3 AtrasosExistem duas opções para organizar atrasos nas corotinas. Para atrasos mais longos e nos casos em que a duração não precisa ser precisa, você pode usar:
async def foo( delay_secs , delay_ms ): await asyncio.sleep ( delay_secs ) print ( 'Hello' ) await asyncio.sleep_ms ( delay_ms )
Durante esses atrasos, o planejador executará outras corotinas. Isso pode introduzir incerteza de tempo, uma vez que a rotina de chamada somente será iniciada quando a que estiver em execução no momento for executada. A quantidade de atraso depende do desenvolvedor do aplicativo, mas provavelmente será da ordem de dezenas ou centenas de ms; isso é discutido mais adiante na interação com dispositivos de hardware (seção 6).
Atrasos muito precisos podem ser executados usando as funções
utime -
sleep_ms e
sleep_us . Eles são mais adequados para pequenos atrasos, pois o agendador não poderá executar outras corotinas enquanto o atraso estiver em andamento.
3.SyncFreqüentemente, é necessário garantir a sincronização entre corotinas. Um exemplo comum é evitar as chamadas "condições de corrida" quando várias corotinas requerem simultaneamente acesso ao mesmo recurso. Um exemplo é fornecido em
astests.py e é discutido na
documentação . Outro perigo é o "abraço da morte", quando cada corotina aguarda a conclusão da outra.
Em aplicativos simples, a sincronização pode ser alcançada usando sinalizadores globais ou variáveis relacionadas. Uma abordagem mais elegante é usar classes de sincronização. O módulo
asyn.py oferece implementações “micro” das classes
Event, Barrier, Semaphore e
Conditios , destinadas a serem usadas apenas com
asyncio . Eles não são orientados a encadeamentos e não devem ser usados com o módulo
_thread ou com o manipulador de interrupções, a menos que especificado de outra forma. A classe
Lock também é implementada, o que é uma alternativa à implementação oficial.
Outro problema de sincronização surge com os produtores e consumidores de corotina. Um produtor de corotina gera dados que um consumidor de corotina usa. Para fazer isso, o
asyncio fornece a classe
Queue . O produtor de corotina coloca os dados na fila, enquanto o consumidor de corotina está aguardando sua conclusão (com outras operações agendadas no prazo). A classe
Queue fornece garantias para remover itens na ordem em que foram recebidos. Como alternativa, você pode usar a classe
Barrier se a corotina produtora precisar esperar até que a corotina consumidora esteja pronta para acessar os dados.
Uma breve visão geral das aulas é dada abaixo. Mais detalhes na
documentação completa .
3.1 BloqueioO bloqueio garante acesso exclusivo a um recurso compartilhado. O exemplo de código a seguir cria uma instância da classe
Lock que é passada a todos os clientes que desejam acessar o recurso compartilhado. Cada corotina tenta capturar o bloqueio, interrompendo a execução até que seja bem-sucedido:
import uasyncio as asyncio from uasyncio.synchro import Lock async def task(i, lock): while 1: await lock.acquire() print("Acquired lock in task", i) await asyncio.sleep(0.5) lock.release() async def killer(): await asyncio.sleep(10) loop = asyncio.get_event_loop() lock = Lock()
3.1.1. Bloqueio e tempos limiteNo momento da redação deste artigo (5 de janeiro de 2018), o desenvolvimento da classe
uasycio Lock ainda não estava oficialmente concluído. Se a corotina tiver um
tempo limite (seção 5.2.2.) , Ao aguardar um bloqueio quando acionado, o tempo limite será ineficaz. Ele não receberá um
TimeoutError até receber um bloqueio. O mesmo se aplica ao cancelamento de uma tarefa.
O módulo
asyn.py oferece a classe
Lock , que funciona nessas situações. Essa implementação da classe é menos eficiente que a classe oficial, mas suporta interfaces adicionais de acordo com a versão do
CPython , incluindo o uso do gerenciador de contexto.
3.2 EventoO evento cria uma oportunidade para uma ou várias corotinas pausarem, enquanto outra dá um sinal sobre sua continuação. Uma instância de
Event fica disponível para todas as corotinas que o utilizam:
import asyn event = asyn.Event ()
Uma corotina aguarda um evento declarando um
evento de espera , após o qual a execução pausa até que outras corotinas declarem
event.set () .
Informação completa .
Um problema pode surgir se
event.set () for emitido em uma construção em loop; o código deve aguardar até que todos os objetos pendentes tenham acesso ao evento antes de configurá-lo novamente. No caso em que um
coro espera um evento, isso pode ser alcançado ao receber um evento
coro limpando o evento:
async def eventwait ( event ): await event event.clear()
A corrotina que aciona o evento verifica se ele foi atendido:
async def foo ( event ): while True :
No caso de vários
coros aguardarem a sincronização de um evento, o problema pode ser resolvido usando o evento de confirmação. Cada
coro precisa de um evento separado.
async def eventwait ( , ack_event ): await event ack_event.set ()
Um exemplo disso é fornecido na função
event_test em
asyntest.py . Isso é complicado Na maioria dos casos - mesmo com um
coro em espera - a classe
Barreira , apresentada abaixo, oferece uma abordagem mais simples.
Um evento também pode fornecer um meio de comunicação entre o manipulador de interrupções e o
coro . O manipulador mantém o hardware e define o evento, que é verificado pelo
coro já no modo normal.
3.2.1 Valores do eventoO método
event.set () pode
usar um valor de dados opcional de qualquer tipo.
Coro , aguardando um evento, pode obtê-lo com
event.value () . Observe que
event.clear () será definido como
None . Um uso típico disso para a configuração de
coro do evento é emitir
event.set (utime.ticks_ms ()) . Qualquer
coro que aguarde um evento pode determinar o atraso que ocorreu, por exemplo, para compensar isso.
3.3 BarreiraExistem dois usos para a classe
Barreira .
Primeiro, ele pode suspender uma corotina até que uma ou várias outras corotinas sejam concluídas.
Em segundo lugar, permite que várias corotinas se encontrem em um determinado ponto. Por exemplo, um produtor e um consumidor podem sincronizar no ponto em que o produtor possui dados e o consumidor está pronto para usá-los. No momento da execução, a
Barreira pode emitir um retorno de chamada adicional antes que a barreira seja removida e todos os eventos pendentes possam continuar.
O retorno de chamada pode ser uma função ou uma rotina. Na maioria das aplicações, a função provavelmente será usada: pode ser garantida a execução antes da conclusão, antes da remoção da barreira.
Um exemplo é a função
barreira_teste em
asyntest.py . No trecho de código deste programa:
import asyn def callback(text): print(text) barrier = asyn.Barrier(3, callback, ('Synch',)) async def report(): for i in range(5): print('{} '.format(i), end='') await barrier
várias instâncias da rotina do
relatório imprimem seus resultados e pausam até que outras instâncias também estejam concluídas e aguardam a
barreira continuar. Neste ponto, um retorno de chamada está sendo feito. Após a conclusão, a rotina original é retomada.
3.4 SemáforoO semáforo limita o número de corotinas que podem acessar o recurso. Pode ser usado para limitar o número de instâncias de uma determinada rotina que pode ser executada simultaneamente. Isso é feito usando um contador de acesso, que é inicializado pelo construtor e reduzido a cada vez que a corotina recebe um semáforo.
A maneira mais fácil de usá-lo em um gerenciador de contexto:
import asyn sema = asyn.Semaphore(3) async def foo(sema): async with sema:
Um exemplo é a função
semaphore_test em
asyntest.py .
3.4.1 Semáforo ( limitado )Funciona de maneira semelhante à classe
Semaphore, exceto que, se o método de
liberação fizer com que o contador de acesso exceda seu valor inicial, um
ValueError será definido.
3.5 FilaA classe
Queue é mantida pelo
uasycio oficial e o programa de amostra
aqtest.py demonstra seu uso. A fila é criada da seguinte maneira:
from uasyncio.queues import Queue q = Queue ()
Uma corotina típica do fabricante pode funcionar da seguinte maneira:
async def producer(q): while True: result = await slow_process()
e a rotina do consumidor pode funcionar da seguinte maneira:
async def consumer(q): while True: result = await(q.get())
A classe
Queue fornece funcionalidade adicional significativa quando o tamanho das filas pode ser limitado e o status pode ser pesquisado. O comportamento com uma fila vazia (se o tamanho for limitado) e o comportamento com uma fila cheia podem ser controlados.
A documentação sobre isso está no código.3.6 Outras classes de sincronizaçãoA biblioteca asyn.py fornece uma micro implementação de alguns dos outros recursos do CPython .A classe Condição permite que uma corotina notifique outras corotinas que aguardam um recurso bloqueado. Após receber a notificação, eles terão acesso ao recurso e serão desbloqueados por vez. Uma corotina de notificação pode limitar o número de corotinas a serem notificadas.A classe Gather permite executar uma lista de corotinas. Após a conclusão deste último, uma lista de resultados será retornada. Essa implementação "micro" usa uma sintaxe diferente. Os tempos limites podem ser aplicados a qualquer uma das corotinas.4 Desenvolvendo classes para assíncioNo contexto do desenvolvimento de drivers de dispositivo, o objetivo é garantir que eles funcionem sem bloqueio. Um driver de corotina deve garantir que outras corotinas sejam executadas enquanto o driver aguarda o dispositivo executar operações de hardware. Por exemplo, uma tarefa aguardando a chegada de dados no UART ou um usuário pressionando um botão deve permitir que outros eventos sejam agendados até que o evento ocorra.4.1 Classes usando aguardar espera Uma corotinapode pausar a execução enquanto aguarda um objeto aguardável . No CPython, uma classe aguardável personalizada é criada implementando o método __await__ especialqual o gerador retorna. A classe aguardável é usada da seguinte maneira: import uasyncio as asyncio class Foo(): def __await__(self): for n in range(5): print('__await__ called') yield from asyncio.sleep(1)
Atualmente MicroPython não suporta __await__ ( edição # 2678 ) e para a solução a ser utilizada __iter__ . A cadeia __iter__ = __await__ fornece portabilidade entre CPython e MicroPython . exemplos de código, consulte as classes de evento, barreira, canceláveis, a condição em asyn.py .4.1.1 Uso em gerenciadores de contextoObjetos esperados podem ser usados em gerenciadores de contexto síncronos ou assíncronos, fornecendo os métodos especiais necessários. Sintaxe: with await awaitable as a:
Para conseguir isso, o gerador __await__ deve retornar self . É transmitida em qualquer variável como a sentença e permite técnicas especiais. Consulte asyn.Condition e asyntest.condition_test onde a classe Condition usa aguardam e podem ser usados em um gerenciador de contexto síncrono.4.1.2 coroutine Await emlinguagem Python requer __await__ era a função do gerador. No MicroPython, os geradores e as rotinas são idênticos; portanto, a solução é usar o rendimento do coro (args) .O objetivo deste guia é oferecer código portável para o CPython 3.5 ou posterior. No CPython, geradores e corotinas são diferentes em significado. No CPython, uma corotina possui um método especial __await__ que o gerador recupera. Isso é portátil: up = False
Observe que __await__, yield de asyncio.sleep (1) é permitido pelo CPython . Ainda não entendo como isso é alcançado.4.2 Iteradoresassíncronos Os iteradores assíncronos fornecem um meio de retornar uma sequência finita ou infinita de valores e podem ser usados como um meio de recuperar elementos de dados sequenciais quando eles vêm de um dispositivo somente leitura. Um iterador assíncrono chama código assíncrono em seu próximo método . A classe deve atender aos seguintes requisitos:- Possui um método __aiter__ definido em def assíncrono e retornando um iterador assíncrono.
- Possui um método __anext__ , que por si só é uma corrotina - isto é, definido via async def e contendo pelo menos uma instrução de espera . Para parar a iteração, ele deve gerar uma exceção StopAsyncIteration .
Os valores de série são recuperados usando assíncrono, conforme mostrado abaixo: class AsyncIterable: def __init__(self): self.data = (1, 2, 3, 4, 5) self.index = 0 async def __aiter__(self): return self async def __anext__(self): data = await self.fetch_data() if data: return data else: raise StopAsyncIteration async def fetch_data(self): await asyncio.sleep(0.1)
4.3 Gerenciadores de contexto assíncronosAs classes podem ser projetadas para oferecer suporte a gerenciadores de contexto assíncronos que possuem procedimentos de entrada e saída que são co-programas. Um exemplo é a classe de bloqueio descrita acima. Possui a rotina __aenter__ , necessária logicamente para a operação assíncrona. Para oferecer suporte ao protocolo assíncrono do gerenciador de contexto, seu método __aexit__ também deve ser uma corotina, o que é alcançado ao incluir waitit asyncio.sleep (0) . Essas classes são acessíveis a partir de uma rotina com a seguinte sintaxe: async def bar ( lock ): async with lock: print ( « bar » )
Como é o caso dos gerenciadores de contexto regulares, é garantido que o método de saída seja chamado quando o gerenciador de contexto concluir seu trabalho, como de costume, e por meio de uma exceção. Para atingir esse objetivo, os métodos especiais __aenter__ e __aexit__ são usados e devem ser definidos como corotinas que aguardam outra corotina ou objeto aguardável . Este exemplo é retirado da classe Lock : async def __aenter__(self): await self.acquire()
Se assíncrona com contém a proposta como com a variável , a variável é atribuído o valor retornado pelo __aenter__ .Para garantir o comportamento correto, o firmware deve ser V1.9.10 ou posterior.5. Exceções a tempos limite e devido ao cancelamento de tarefasEstes tópicos estão relacionados: o uasyncio inclui cancelar tarefas e aplicar um tempo limite a uma tarefa, lançando uma exceção para a tarefa de uma maneira especial.5.1 ExceçõesSe ocorrer uma exceção em uma corotina, ela deve ser processada nessa corotina ou em uma corotina aguardando sua conclusão. Isso garante que a exceção não se aplique ao planejador. Se ocorrer uma exceção, o planejador interromperá o trabalho passando a exceção para o código iniciado pelo planejador. Portanto, para evitar que o planejador pare, a corotina iniciada com loop.create_task () deve capturar quaisquer exceções dentro.Usar throw ou close para lançar uma exceção em uma rotina é irracional. Isso destrói o uasyncio , fazendo com que a corotina inicie e possivelmente saia quando ainda está na fila de execução.O exemplo acima ilustra essa situação. Se permitido trabalhar até o fim, funcionará conforme o esperado. import uasyncio as asyncio async def foo(): await asyncio.sleep(3) print('About to throw exception.') 1/0 async def bar(): try: await foo() except ZeroDivisionError: print('foo - 0')
No entanto, emitir uma interrupção do teclado faz com que a exceção entre no loop de eventos. Isso ocorre porque a execução do uasyncio.sleep é passada para o loop de eventos. Portanto, os aplicativos que exigem um código claro em resposta a uma interrupção do teclado devem capturar uma exceção no nível do loop de eventos.5.2 Cancelamento e tempos limiteComo mencionado acima, essas funções funcionam lançando uma exceção para a tarefa de uma maneira especial usando o método especial MicroPython coroutine pend_throw . Como funciona depende da versão. No uasyncio oficial v.2.0, uma exceção não é processada até a próxima tarefa agendada. Isso impõe um atraso se a tarefa espera dormirentrada-saída Os tempos limite podem ir além do período nominal. A tarefa de desfazer de outras tarefas não pode determinar quando o desfazer é concluído.Atualmente, há uma solução alternativa e duas soluções.- Solução alternativa : a biblioteca assíncrona fornece um meio de aguardar o cancelamento de tarefas ou grupos de tarefas. Consulte Cancelar um trabalho (seção 5.2.1.).
- A biblioteca Paul Sokolovsky fornece o uasyncio v2.4 , mas isso requer seu firmware Pycopy .
- Fast_io biblioteca uasyncio resolve este problema no Python (menos forma elegante) e rodando firmware oficial.
A hierarquia de exceções usada aqui é Exception-CanceledError-TimeoutError .5.2.1 Cancelando um trabalho Ouasyncio fornece uma função de cancelamento (coro) . Isso funciona lançando uma exceção para usar a corotina pend_throw . Também funciona com corotinas aninhadas. O uso é o seguinte: async def foo(): while True:
Se este exemplo é executado sob uasyncio v2.0 , quando o bar vai emitir cancelar isso não terá efeito até a próxima agendada foo e casos foo pode haver um atraso de até 10 segundos. Outra fonte de atraso ocorrerá se foo estiver aguardando E / S. Onde quer que o atraso ocorra, a barra não poderá determinar se o foo foi cancelado. É importante em alguns casos de uso.Ao usar as bibliotecas Paul Sokolovsky ou fast_io, é suficiente usar sleep (0): async def foo(): while True:
Isso também funcionará no uasyncio v2.0 se foo (e qualquer coroutine pendente foo ) nunca retornasse o sono e não esperasse E / S.Comportamento que pode surpreender o descuidado ocorre quando é esperado que uma corotina executada por create_task e no modo de espera cancele . Considere este trecho: async def foo(): while True:
Quando foo é cancelado, ele é removido da fila do planejador; porque falta uma declaração de retorno , o procedimento de chamada foo_runner nunca é retomado. É recomendável que você sempre capture a exceção no escopo mais externo da função a ser desfeita: async def foo(): try: while True: await asyncio.sleep(10) await my_coro except asyncio.CancelledError: return
Nesse caso, o my_coro não precisa capturar a exceção, pois ela será propagada para o canal de chamada e capturada lá.Nota
É proibido usar métodos close ou throw de corotinas quando elas são usadas fora do planejador. Isso prejudica o agendador, forçando a corotina a executar o código, mesmo que não esteja agendado. Isso pode ter consequências indesejáveis.5.2.2 coroutines com tempos limiteTimeouts implementado usando uasyncio métodos .wait_for () e .wait_for_ms () . Eles tomam a rotina e a latência em segundos ou ms, respectivamente, como argumentos. Se o tempo limite expirar, um TimeoutError será lançado na corotina usando pend_throw. Essa exceção deve ser detectada pelo usuário ou pelo chamador. Isso é necessário pelo motivo descrito acima: se o tempo limite expirar, ele será cancelado. A menos que o erro seja capturado e retornado, a única maneira de o chamador continuar é capturar a própria exceção.Onde a exceção foi capturada pela corotina, tive falhas pouco claras se a exceção não foi capturada no escopo externo, conforme mostrado abaixo: import uasyncio as asyncio async def forever(): try: print('Starting') while True: await asyncio.sleep_ms(300) print('Got here') except asyncio.TimeoutError: print('Got timeout')
Como alternativa, você pode interceptar a função de chamada: import uasyncio as asyncio async def forever(): print('Starting') while True: await asyncio.sleep_ms(300) print('Got here') async def foo(): try: await asyncio.wait_for(forever(), 5) except asyncio.TimeoutError: pass print('Timeout elapsed.') await asyncio.sleep(2) loop = asyncio.get_event_loop() loop.run_until_complete(foo())
Nota para o Uasyncio v2.0 .Isso não se aplica às bibliotecas Paul Sokolovsky ou fast_io .Se a corotina iniciar, aguarde asyncio.sleep (t) , com um longo atraso t, a corotina não será reiniciada até t expirar . Se o tempo limite expirar antes que o sono termine , ocorrerá um erro de tempo limite quando a corotina for recarregada - ou seja, quando t expirar . Em tempo real e da perspectiva do chamador, sua resposta TimeoutError será atrasada.Se isso for importante para o aplicativo, crie um longo atraso enquanto aguarda um curto no loop. Coroutineo asyn.sleep suporta isso.6 Interação com o equipamentoA base da interação entre uasyncio e eventos assíncronos externos é a pesquisa de opinião . O hardware que requer uma resposta rápida pode usar uma interrupção. Mas a interação entre a rotina de interrupção (ISR) e a rotina do usuário será baseada em pesquisas. Por exemplo, um ISR pode chamar Event ou definir um sinalizador global, enquanto uma corrotina que aguarda um resultado pesquisa um objeto cada vez que uma solicitação é agendada.A interrogação pode ser realizada de duas maneiras, explícita ou implícita. O último é feito usando E / S de fluxoum mecanismo, que é um sistema projetado para dispositivos de streaming como UART e soquetes . Na pesquisa explícita mais simples, o seguinte código pode consistir: async def poll_my_device(): global my_flag
Em vez de um sinalizador global, você pode usar uma variável de instância da classe Event ou uma instância de uma classe que use wait . Uma pesquisa explícita é discutida abaixo.A pesquisa implícita consiste em desenvolver um driver que funcionará como um dispositivo de E / S de streaming, como um soquete UART ou E / S de streaming , que pesquisa os dispositivos usando o sistema Python select.poll : como a pesquisa é realizada em C, é mais rápida e eficiente do que pesquisa explícita. O uso de E / S de fluxo é discutido na seção 6.3. Devido à sua eficácia, a pesquisa implícita oferece uma vantagem aos drivers de dispositivo de E / S mais rápidos: os drivers de streaming podem ser criados para muitos dispositivos que geralmente não são considerados dispositivos de streaming. Isso é discutido em mais detalhes na seção 6.4.6.1 Problemas de sincronizaçãoAtualmente, pesquisas explícitas e implícitas são baseadas em planejamento cíclico. Suponha que a E / S funcione simultaneamente com N rotinas personalizadas, cada uma das quais é executada com atraso zero. Quando a E / S é servida, ela será pesquisada assim que todas as operações do usuário forem agendadas. O atraso estimado deve ser considerado ao projetar. Os canais de E / S podem exigir buffer, com o equipamento de manutenção ISR em tempo real a partir de buffers e corotinas, preenchendo ou liberando buffers em um tempo mais lento.Também é necessário considerar a possibilidade de ir além: esse é o caso quando algo interrogado pela corotina acontece mais de uma vez antes de ser realmente planejado pela corotina.Outra questão de tempo é a precisão da latência. Se a corotina emitir await asyncio.sleep_ms ( t )
o planejador garante que a execução seja suspensa por pelo menos t ms. O atraso real pode ser maior que t, dependendo da carga atual do sistema. Se, nesse momento, outras corotinas aguardarem a conclusão de atrasos diferentes de zero, a próxima linha será imediatamente agendada para execução. Porém, se outras corotinas também estiverem aguardando execução (porque emitiram um atraso zero ou porque o tempo também expirou), elas podem ser agendadas para serem executadas mais cedo. Isso introduz incerteza de sincronização nas funções sleep () e sleep_ms () . O valor do pior caso para esse estouro pode ser calculado somando os valores de tempo de execução de todas essas rotinas para determinar o tempo de transmissão do pior caso para o planejador.A versão fast_io do uasyncio , neste contexto, fornece uma maneira de garantir que a E / S de streaming seja pesquisada a cada iteração do planejador. Espera-se que o uasyncio oficial aceite as emendas relevantes em devido tempo.6.2 Interrogando dispositivos usando corotinasEsta é uma abordagem simples, mais adequada para dispositivos que podem ser interrogados a uma velocidade relativamente baixa. Isso ocorre principalmente porque a pesquisa com um intervalo de pesquisa curto (ou zero) pode levar ao fato de que a corotina consome mais tempo do processador do que o desejável para cair no intervalo.O exemplo apoll.py demonstra essa abordagem consultando o acelerômetro Pyboardcom um intervalo de 100 ms. Ele executa uma filtragem simples para ignorar o ruído e imprime uma mensagem a cada dois segundos, se nenhum movimento ocorrer.O exemplo aswitch.py fornece drivers para comutadores e dispositivos de botão.Um exemplo de driver para um dispositivo capaz de ler e escrever é mostrado abaixo. Para facilitar o teste, o Pyboard UART 4 emula um dispositivo condicional. Driver implementa a classe RecordOrientedUart, em que os dados são fornecidos em registros de tamanho variável que consistem em instâncias de bytes. O objeto adiciona um delimitador antes de enviar e armazena em buffer os dados recebidos até que um delimitador adicionado seja recebido. Esta é apenas uma demonstração e uma maneira ineficiente de usar o UART em comparação com a entrada / saída de streaming.Para demonstrar a transferência assíncrona, assumimos que o dispositivo emulado tem um meio de verificar se a transferência está concluída e que o aplicativo exige que esperemos. Nenhuma das suposições é verdadeira neste exemplo, mas o código o falsifica aguardando asyncio.sleep (0.1) .Para começar, não esqueça de conectar as saídas do Pyboard X1 e X2 (UART Txd e Rxd) import uasyncio as asyncio from pyb import UART class RecordOrientedUart(): DELIMITER = b'\0' def __init__(self): self.uart = UART(4, 9600) self.data = b'' def __iter__(self):
6.3 Usando o mecanismo de streaming ( Stream )O exemplo demonstra a entrada e saída simultânea em um UART do microprocessador Pyboard .Para começar, conecte as saídas do Pyboard X1 e X2 (UART Txd e Rxd) import uasyncio as asyncio from pyb import UART uart = UART(4, 9600) async def sender(): swriter = asyncio.StreamWriter(uart, {}) while True: await swriter.awrite('Hello uart\n') await asyncio.sleep(2) async def receiver(): sreader = asyncio.StreamReader(uart) while True: res = await sreader.readline() print('Received', res) loop = asyncio.get_event_loop() loop.create_task(sender()) loop.create_task(receiver()) loop.run_forever()
O código de suporte pode ser encontrado em __init__.py na biblioteca uasyncio . O mecanismo funciona porque o driver de dispositivo (escrito em C ) implementa os seguintes métodos: ioctl, read, readline e write . Seção 6.4: A criação de um driver de dispositivo de streaming revela detalhes de como esses drivers podem ser escritos em Python .O UART pode receber dados a qualquer momento. O mecanismo de E / S de streaming verifica se há caracteres pendentes sempre que o agendador obtém controle. Quando a corotina está em execução, a rotina de interrupção armazena em buffer os caracteres recebidos; eles serão excluídos quando a corotina der lugar ao agendador. Portanto, os aplicativos UART devem ser projetados para que as corotinas minimizem o tempo entre as transferências para o planejador, a fim de evitar estouros de buffer e perda de dados. Isso pode ser aprimorado usando um buffer de leitura UART maior ou uma taxa de dados mais baixa. Como alternativa, o controle de fluxo de hardware fornecerá uma solução se a fonte de dados o suportar.6.3.1 Exemplo de UART controladorprograma auart_hd.pyilustra um método de comunicação com um dispositivo half-duplex, como um dispositivo que responde ao conjunto de comandos do modem AT. Half duplex significa que o dispositivo nunca envia dados não solicitados: suas transferências são sempre realizadas em resposta a um comando recebido do mestre.O dispositivo é emulado executando um teste em um Pyboard com duas conexões com fio.O dispositivo emulado (muito simplificado) responde a qualquer comando enviando quatro linhas de dados com uma pausa entre cada uma para simular o processamento lento.O assistente envia um comando, mas não sabe antecipadamente quantas linhas de dados serão retornadas. Ele inicia um timer de reinicialização que é reiniciado toda vez que uma linha é recebida. Quando o cronômetro expirar, presume-se que o dispositivo tenha concluído a transmissão e uma lista de linhas recebidas seja retornada.Também é demonstrado um caso de falha do dispositivo, o que é alcançado pulando uma transmissão antes de esperar por uma resposta. Após o tempo limite, uma lista vazia é retornada. Veja os comentários do código para mais detalhes.6.4 Desenvolvimento de fluxo contínuo (drivers de fluxo ) da unidadede entrada de fluxo / mecanismo de saída ( fluxo de I / O ) para controlar o funcionamento da transmissão de dispositivos I / O, tais como UART e soquetes ( soquete) O mecanismo pode ser usado pelos drivers de qualquer dispositivo consultado regularmente, delegando ao agendador que usa select , consultando a disponibilidade de qualquer dispositivo na fila. Isso é mais eficiente do que executar várias operações de corotina, cada uma pesquisando o dispositivo, em parte porque select é gravada em C e também porque a corotina que realiza a pesquisa é atrasada até que o objeto pesquisado retorne um estado pronto.Um driver de dispositivo capaz de atender ao mecanismo de entrada / saída de streaming deve preferencialmente suportar os métodos StreamReader, StreamWriter. Um dispositivo legível deve fornecer pelo menos um dos seguintes métodos. Observe que esses são métodos síncronos. O método ioctl (veja abaixo) garante que eles sejam chamados apenas quando os dados estiverem disponíveis. Os métodos devem ser retornados o mais rápido possível, usando o máximo de dados disponíveis.readline () Retorna o máximo de caracteres possível, até qualquer caractere de nova linha. Necessário se você estiver usando StreamReader.readline ()read (n) Retorne o máximo de caracteres possível, mas não mais que n . Necessário se estiver usando StreamReader.read () ou StreamReader.readexactly ()O driver criado deve fornecer o seguinte método síncrono com retorno imediato:escreva com argumentos buf, off, sz .Onde:buf é o buffer para gravação.off - offset para o buffer do primeiro caractere a ser gravado.sz - o número solicitado de caracteres para escrever.O valor de retorno é o número de caracteres realmente gravados (talvez 1 se o dispositivo estiver lento).O método ioctl garante que ele será chamado apenas quando o dispositivo estiver pronto para receber dados.Todos os dispositivos devem fornecer um método ioctl que controla o equipamento para determinar seu status de disponibilidade. Um exemplo típico para um driver de leitura / gravação: import io MP_STREAM_POLL_RD = const(1) MP_STREAM_POLL_WR = const(4) MP_STREAM_POLL = const(3) MP_STREAM_ERROR = const(-1) class MyIO(io.IOBase):
A seguir, é apresentada uma descrição do atraso de espera da classe MillisecTimer : import uasyncio as asyncio import utime import io MP_STREAM_POLL_RD = const(1) MP_STREAM_POLL = const(3) MP_STREAM_ERROR = const(-1) class MillisecTimer(io.IOBase): def __init__(self): self.end = 0 self.sreader = asyncio.StreamReader(self) def __iter__(self): await self.sreader.readline() def __call__(self, ms): self.end = utime.ticks_add(utime.ticks_ms(), ms) return self def readline(self): return b'\n' def ioctl(self, req, arg): ret = MP_STREAM_ERROR if req == MP_STREAM_POLL: ret = 0 if arg & MP_STREAM_POLL_RD: if utime.ticks_diff(utime.ticks_ms(), self.end) >= 0: ret |= MP_STREAM_POLL_RD return ret
que pode ser usado da seguinte maneira: async def timer_test ( n ): timer = ms_timer.MillisecTimer () await timer ( 30 )
Comparado ao uasyncio oficial , essa implementação não oferece nenhuma vantagem em comparação com o asyncio.sleep_ms () . O uso de fast_io fornece atrasos significativamente mais precisos no padrão de uso normal, quando as corotinas esperam um atraso zero.Você pode usar o agendamento de E / S para associar um evento a um retorno de chamada. Isso é mais eficiente que o ciclo de pesquisa, porque a pesquisa não é agendada até que o ioctl retorne pronto. Em seguida, um retorno de chamada é executado quando o retorno de chamada muda de estado. import uasyncio as asyncio import io MP_STREAM_POLL_RD = const(1) MP_STREAM_POLL = const(3) MP_STREAM_ERROR = const(-1) class PinCall(io.IOBase): def __init__(self, pin, *, cb_rise=None, cbr_args=(), cb_fall=None, cbf_args=()): self.pin = pin self.cb_rise = cb_rise self.cbr_args = cbr_args self.cb_fall = cb_fall self.cbf_args = cbf_args self.pinval = pin.value() self.sreader = asyncio.StreamReader(self) loop = asyncio.get_event_loop() loop.create_task(self.run()) async def run(self): while True: await self.sreader.read(1) def read(self, _): v = self.pinval if v and self.cb_rise is not None: self.cb_rise(*self.cbr_args) return b'\n' if not v and self.cb_fall is not None: self.cb_fall(*self.cbf_args) return b'\n' def ioctl(self, req, arg): ret = MP_STREAM_ERROR if req == MP_STREAM_POLL: ret = 0 if arg & MP_STREAM_POLL_RD: v = self.pin.value() if v != self.pinval: self.pinval = v ret = MP_STREAM_POLL_RD return ret
E novamente - no uasyncio oficial , o atraso pode ser alto. Dependendo do design do aplicativo, a versão fast_io pode ser mais eficiente.A demonstração do iorw.py ilustra um exemplo completo. Observe que, no momento da redação do artigo no uasyncio oficial, há um erro devido ao qual isso não funciona . Existem duas soluções. A solução alternativa é gravar dois drivers separados, um para somente leitura e outro para somente gravação. O segundo é usar o fast_io , que resolve esse problema.No uasyncio oficial , a entrada / saída é planejada muito raramente .6.5 Exemplo completo: aremote.pyO driver foi projetado para receber / decodificar sinais de um controle remoto infravermelho. O próprio driver aremote.py . As notas a seguir são significativas em relação ao uso de assíncio .A interrupção no contato registra a hora da alteração de estado (em microssegundos) e define o evento, ignorando a hora em que a primeira alteração de estado ocorreu. A corotina aguarda um evento, relata a duração do pacote de dados e decodifica os dados armazenados antes de chamar o retorno de chamada especificado pelo usuário.A passagem do tempo para uma instância de Evento permite que a corotina compense qualqueratraso assíncrono ao definir o período de atraso.6.6 sensor ambiental HTU21DO driver de chip HTU21D fornece medições precisas de temperatura e umidade.O chip precisa de cerca de 120 ms para receber os dois elementos de dados. O driver trabalha de forma assíncrona, iniciando o recebimento e o uso de wait asyncio.sleep (t) antes de ler os dados, atualiza as variáveis de temperatura e umidade, que podem ser acessadas a qualquer momento, o que permite que outras corotinas sejam ativadas enquanto o driver do chip estiver em execução.7.Dicas e truques7.1 O programa congela O congelamentogeralmente ocorre porque a tarefa é bloqueada sem concessão: isso levará ao congelamento de todo o sistema. Ao desenvolver, é útil ter uma rotina rotativa que acenda periodicamente o LED embutido. Isso fornece confirmação de que o planejador ainda está em execução.7.2 uasyncio salva estadoAo iniciar programas usando o uasyncio no REPL, execute uma reinicialização suave (ctrl-D) entre as partidas. Devido ao fato de o uasyncio manter o estado entre as partidas, um comportamento imprevisível pode ocorrer na próxima partida.7.3 Coleta de lixoVocê pode executar uma corotina especificando import gc primeiro : gc.collect () gc.treshold ( gc.mem_free () // 4 + gc.mem_alloc ())
O objetivo disso é discutido aqui na seção heap.7.4 TesteÉ recomendável garantir que o driver do dispositivo mantenha o controle quando necessário, o que pode ser feito executando uma ou mais cópias de corotinas fictícias que iniciam o ciclo de impressão de mensagens e verificando se ele é executado durante períodos em que o driver está no modo de espera: async def rr(n): while True: print('Roundrobin ', n) await asyncio.sleep(0)
Como um exemplo do tipo de perigo que pode surgir, no exemplo acima, o método RecordOrientedUart __await__ foi originalmente escrito como: def __await__(self): data = b'' while not data.endswith(self.DELIMITER): while not self.uart.any(): yield from asyncio.sleep(0) data = b''.join((data, self.uart.read(self.uart.any()))) self.data = data
Como resultado, a execução é esticada até que todo o registro seja recebido, bem como o fato de que uart.any () sempre retorna um número diferente de zero de caracteres recebidos. No momento da chamada, todos os caracteres já podem ter sido recebidos. Esta situação pode ser resolvida usando um loop externo: def __await__(self): data = b'' while not data.endswith(self.DELIMITER): yield from asyncio.sleep(0)
Vale a pena notar que esse erro não seria óbvio se os dados tivessem sido enviados para o UART em uma velocidade mais baixa, em vez de usar um teste de feedback. Bem-vindo às alegrias da programação em tempo real.7.5 Erro comumSe uma função ou método é definido por def assíncrono e subsequentemente chamado como se fosse uma chamada regular (síncrona), o MicroPython não exibe uma mensagem de erro. Isso é por design. Geralmente, isso leva ao fato de que o programa silenciosamente não funciona corretamente: async def foo():
Eu tenho uma sugestão que sugere corrigir a situação na opção 1 usando fast_io .O módulo check_async_code.py tenta detectar casos de uso duvidoso de corotinas. Está escrito em Python3 e foi projetado para funcionar em um PC. Utilizado em scripts escritos de acordo com as diretrizes descritas neste guia com corotinas declaradas usando async def . O módulo usa um argumento, o caminho para o arquivo MicroPython de origem (ou --help).Observe que é um tanto rude e se destina a ser usado em um arquivo sintaticamente correto, que não inicia por padrão. Use uma ferramenta, como o pylint, para verificar a sintaxe geral (o pylint atualmente não possui esse erro).O script produz falsos positivos. De acordo com o plano, as corotinas são objetos de primeiro nível, podem ser transferidas para funções e armazenadas em estruturas de dados. Dependendo da lógica do programa, você pode salvar a função ou o resultado de sua execução. O script não pode determinar a intenção. O objetivo é ignorar os casos que parecem corretos ao identificar outros casos a serem considerados. Suponha foo onde a corotina é declarada como def assíncrona : loop.run_until_complete(foo())
Acho útil, mas as melhorias são sempre bem-vindas.7.6 Programação com sockets ( soquetes )Existem duas abordagens básicas para a programação soquetes uasyncio . Por padrão, os soquetes são bloqueados até que a operação de leitura ou gravação especificada seja concluída. O Uasyncio suporta o bloqueio de soquete usando o select.poll para impedir que o planejador os bloqueie. Na maioria dos casos, esse mecanismo é mais fácil de usar. Um exemplo de código do cliente e do servidor pode ser encontrado no diretório client_server . Userver usa o aplicativo select.poll pesquisando explicitamente o soquete do servidor.Os soquetes do cliente o usam implicitamente no sentido em que o mecanismo de streaming uasyncio o usa diretamente.Observe que o socket.getaddrinfo está atualmente bloqueado. O tempo no código de exemplo será mínimo, mas se for necessária uma pesquisa de DNS, o período de bloqueio poderá ser significativo.Uma segunda abordagem para a programação de soquetes é usar soquetes sem bloqueio. Isso aumenta a complexidade, mas é necessário em alguns aplicativos, especialmente se a conexão for via Wi-Fi (veja abaixo).No momento da redação deste artigo (março de 2019), o suporte ao TLS para soquetes sem bloqueio estava em desenvolvimento. Seu status exato é desconhecido (para mim).O uso de soquetes sem bloqueio exige alguma atenção aos detalhes. Se leituras sem bloqueio ocorrerem devido à latência do servidor, não há garantia de que todos (ou alguns) dos dados solicitados serão retornados. Da mesma forma, as entradas podem não ser concluídas.Portanto, os métodos assíncronos de leitura e gravação devem executar iterativamente uma operação sem bloqueio até que os dados necessários sejam lidos ou gravados. Na prática, pode ser necessário um tempo limite para lidar com interrupções no servidor.Outra complicação é que a porta ESP32 teve problemas que exigiam invasões bastante desagradáveis para uma operação sem erros. Não testei se esse ainda é o caso.Módulo Sock_nonblock.pyilustra os métodos necessários. Esta não é uma demonstração funcional e é provável que as decisões sejam dependentes do aplicativo.7.6.1 Problemas com o WiFiO mecanismo de streaming uasyncio não é a melhor opção ao detectar falhas no WiFi. Achei necessário usar soquetes sem bloqueio para fornecer operação à prova de falhas e reconectar o cliente em caso de falhas.Este documento descreve os problemas encontrados em aplicativos WiFi que mantêm os soquetes abertos por longos períodos e descrevem a solução.Pltcmoferece um cliente MQTT assíncrono robusto que fornece integridade de mensagem durante falhas de WiFi. É descrito um link serial full-duplex assíncrono simples entre um cliente sem fio e um servidor com fio com entrega garantida de mensagens.7.7 Argumentos do construtor de loop de eventosPode ocorrer um pequeno erro se você precisar criar um loop de eventos com valores diferentes dos valores padrão. Esse loop deve ser declarado antes de executar qualquer outro código usando assíncrono, pois esses valores podem ser necessários nesse código. Caso contrário, o código será inicializado com os valores padrão: import uasyncio as asyncio import some_module bar = some_module.Bar()
Como a importação de um módulo pode executar código, a maneira mais segura é instanciar um loop de eventos imediatamente após a importação do uasyncio . import uasyncio as asyncio loop = asyncio.get_event_loop(runq_len=40, waitq_len=40) import some_module bar = some_module.Bar()
Ao escrever módulos para uso por outros programas, prefiro evitar a execução de código uasyncio na importação. Escreva funções e métodos para aguardar um loop de eventos como argumento. Em seguida, verifique se apenas os aplicativos de nível superior chamam get_event_loop : import uasyncio as asyncio import my_module
Esta questão é discutida aqui .8 notas para iniciantesEssas notas destinam-se a iniciantes em código assíncrono e começam com uma descrição dos problemas que os planejadores tentam resolver, além de fornecer uma visão geral da abordagem de soluções do uasyncio .A Seção 8.5 discute os méritos relativos dos módulos uasyncio e _ thread , bem como por que você pode preferir usar as corotinas uasyncio com agendamento proativo (_thread).8.1 Problema 1: loops de eventosUm aplicativo típico de firmware funciona continuamente e, ao mesmo tempo, deve responder a eventos externos, que podem incluir uma alteração de voltagem no ADC, a aparência de uma interrupção de hardware ou um símbolo recebido no UART ou dados disponíveis no soquete. Esses eventos ocorrem de forma assíncrona e o código deve poder responder independentemente da ordem em que ocorrem. Além disso, podem ser necessárias tarefas dependentes do tempo, como LEDs piscando.A maneira óbvia de fazer isso é com o loop de eventos uasycio . Este exemplo não é um código prático, mas serve para ilustrar a forma geral do loop de eventos. def event_loop(): led_1_time = 0 led_1_period = 20 led_2_time = 0 led_2_period = 30 switch_state = switch.state()
Esse loop funciona para exemplos simples, mas à medida que o número de eventos aumenta, o código rapidamente se torna complicado. Eles também violam os princípios da programação orientada a objetos combinando a maior parte da lógica do programa em um só lugar, em vez de vincular o código a um objeto controlado. Queremos desenvolver uma classe para um LED intermitente que possa ser inserido em um módulo e importado. A abordagem OOP do LED piscando pode ser assim: import pyb class LED_flashable(): def __init__(self, led_no): self.led = pyb.LED(led_no) def flash(self, period): while True: self.led.toggle()
O planejador no uasyncio permite criar essas classes.8.2 Problema 2: métodos de bloqueioSuponha que você precise ler um determinado número de bytes de um soquete. Se você chamar socket.read (n) com um soquete de bloqueio por padrão, ele "bloqueará" (ou seja, não poderá terminar) até que n bytes sejam recebidos . Durante esse período, o aplicativo não responderá a outros eventos.Usando o soquete uasyncio sem bloqueio , você pode escrever um método de leitura assíncrono. Uma tarefa que requer dados será (necessariamente) bloqueada até que seja recebida, mas outras tarefas serão executadas durante esse período, o que permitirá que o aplicativo permaneça responsivo.8.3 Abordagens de UasyncioA próxima aula tem um LED que pode ser ligado e desligado, e você também pode piscar a qualquer velocidade. A instância LED_async usa o método run , que pode ser usado para operação contínua. O comportamento dos LEDs pode ser controlado usando os métodos ligado (), desligado () e flash (segundos) . import pyb import uasyncio as asyncio class LED_async(): def __init__(self, led_no): self.led = pyb.LED(led_no) self.rate = 0 loop = asyncio.get_event_loop() loop.create_task(self.run()) async def run(self): while True: if self.rate <= 0: await asyncio.sleep_ms(200) else: self.led.toggle() await asyncio.sleep_ms(int(500 / self.rate)) def flash(self, rate): self.rate = rate def on(self): self.led.on() self.rate = 0 def off(self): self.led.off() self.rate = 0
Note-se que on (), off () e flash () são métodos síncronos comuns. Eles mudam o comportamento do LED, mas retornam imediatamente. Piscando ocorre "em segundo plano". Isso é explicado em detalhes na próxima seção.A classe está em conformidade com o princípio OOP, que consiste em armazenar a lógica associada ao dispositivo na classe. Ao mesmo tempo, o uso do uasyncio garante que o aplicativo possa responder a outros eventos enquanto o LED estiver piscando. O programa abaixo pisca com quatro LEDs do Pyboard em diferentes frequências e também responde ao botão USR, que o completa. import pyb import uasyncio as asyncio from led_async import LED_async
Ao contrário do primeiro exemplo de um loop de eventos, a lógica associada ao comutador está em uma função separada da funcionalidade do LED. Preste atenção ao código usado para iniciar o planejador: loop = asyncio.get_event_loop() loop.run_until_complete(killer())
8.4 Planejamento no uasyncioPython 3.5 e MicroPython suportam o conceito de uma função assíncrona, também conhecida como uma rotina ou tarefa. Uma corrotina deve incluir pelo menos uma declaração de espera . async def hello(): for _ in range(10): print('Hello world.') await asyncio.sleep(1)
Esta função imprime uma mensagem dez vezes em intervalos de um segundo. Enquanto a função é pausada em antecipação a um atraso, o agendador assíncrono executará outras tarefas, criando a ilusão de executá-las simultaneamente.Quando um problema de rotineira aguarda asyncio.sleep_ms () ou asyncio.sleep (), a tarefa atual é pausada e colocada em uma fila que é ordenada por horário e a execução prossegue para a tarefa na parte superior da fila. A fila é projetada de maneira que, mesmo que o modo de suspensão especificado seja zero, outras tarefas relevantes serão executadas até que a corrente seja retomada. Esse é um planejamento "circular honesto". É prática comum executar loops aguardando asyncio.sleep (0) .para que a tarefa não atrase a execução. A seguir, um loop de espera ocupada aguardando outra tarefa para definir a variável de flag global . Infelizmente, monopoliza o processador, impedindo o lançamento de outras corotinas: async def bad_code(): global flag while not flag: pass
O problema aqui é que, até que o loop flag flag False passe o controle para o planejador, nenhuma outra tarefa será iniciada. A abordagem correta: async def good_code(): global flag while not flag: await asyncio.sleep(0)
Pelo mesmo motivo, é uma má prática definir atrasos, por exemplo, utime.sleep (1) porque bloqueia outras tarefas por 1 s; é mais correto usar o wait asyncio.sleep (1) .Observe que os atrasos gerados pelos métodos uasyncio sleep e sleep_ms podem realmente exceder o tempo especificado. Isso se deve ao fato de que outras tarefas serão executadas durante o atraso. Após o período de atraso, a execução não será retomada até que os problemas da tarefa em execução aguardem ou sejam finalizados. Uma rotina bem comportada sempre declara aguardarem intervalos regulares. Nos casos em que é necessário um atraso exato, especialmente se houver menos de alguns ms, pode ser necessário usar utime.sleep_us (us) .8.5 Por que agendamento colaborativo, não baseado em encadeamento ( _thread )?A reação inicial dos iniciantes à ideia de co-planejar corotinas é muitas vezes decepcionante. Certamente o planejamento de streaming é melhor? Por que eu deveria ceder explicitamente o controle se a máquina virtual Python pode fazer isso por mim?Quando se trata de sistemas embarcados, o modelo de colaboração tem duas vantagens.O primeiro é leve. É possível ter um grande número de corotinas, porque, diferentemente dos encadeamentos agendados, as corotinas suspensas ocupam menos espaço.Em segundo lugar, isso evita alguns dos problemas sutis associados ao agendamento de streaming.Na prática, a multitarefa colaborativa é amplamente usada, especialmente em aplicativos de interface com o usuário.Em defesa do modelo de planejamento de streaming, mostrarei uma vantagem: se alguém escrever for x in range ( 1000000 ):
não irá bloquear outras tarefas. O modelo de colaboração pressupõe que o loop conceda explicitamente ao controle de cada tarefa um certo número de iterações, por exemplo, colocando código em uma rotina e emitindo periodicamente aguardando asyncio.sleep (0) .Infelizmente, essa vantagem empalidece em comparação com as desvantagens. Alguns deles são descritos na documentação para escrever manipuladores de interrupção.. Em um modelo de agendamento de streaming, cada thread pode interromper qualquer outro thread, alterando os dados que podem ser usados em outros threads. Como regra, é muito mais fácil encontrar e corrigir um bloqueio que ocorre devido a um erro que não gera resultado do que a detecção de erros às vezes muito sutis e raramente encontrados, que podem ocorrer no código escrito na estrutura de um modelo com planejamento de streaming.Simplificando, se você escrever uma corotina MicroPython , pode ter certeza de que as variáveis não serão alteradas repentinamente por outra corotina: sua corotina tem controle total até retornar enquanto aguarda asyncio.sleep (0) .Lembre-se de que os manipuladores de interrupção são preventivos. Isso se aplica às interrupções de hardware e software que podem ocorrer em qualquer lugar do seu código.Uma discussão eloquente sobre problemas de planejamento de streaming pode ser encontrada aqui .8.6 InteraçãoEm aplicações não triviais, as corotinas devem interagir. Métodos convencionais de Python podem ser usados . Isso inclui o uso de variáveis globais ou a declaração de corotinas como métodos de objeto: eles podem compartilhar variáveis de instância. Como alternativa, um objeto mutável pode ser passado como argumento para uma corotina.O modelo de planejamento de streaming requer especialistas para garantir que as classes forneçam uma conexão segura; em um modelo de colaboração, isso raramente é necessário.8.7 Poll ( polling )Alguns dispositivo de hardware, tais como um acelerómetro Pyboard , não suportam as interrupções e, portanto, devem ser consultadas (i periodicamente verificado). A pesquisa também pode ser usada em conjunto com manipuladores de interrupção: o manipulador de interrupção mantém o equipamento e define um sinalizador. A corotina consulta o sinalizador - se estiver definido, os dados são processados e o sinalizador é redefinido. A melhor abordagem é usar a classe Event .