Mempelajari kemungkinan MicroPython untuk keperluannya, saya menemukan salah satu implementasi perpustakaan asyncio dan, setelah korespondensi singkat dengan Piter Hinch , penulis perpustakaan, saya menyadari bahwa saya perlu memahami lebih dalam prinsip, konsep dasar dan kesalahan umum menggunakan metode pemrograman asinkron. Apalagi bagian untuk pemula hanya untuk saya.Panduan ini ditujukan untuk pengguna dengan tingkat pengalaman berbeda dengan
asyncio , termasuk bagian khusus untuk pemula.
Isi0. Pendahuluan0.1
.___ Menginstal
uasyncio pada perangkat kosong (perangkat keras)
1. Merencanakan pelaksanaan program bersama1.1 .___ Modul
2. perpustakaan uasyncio2.1 .___ Struktur Program: Siklus Pemrosesan Acara
2.2 .___ Coroutines
2.2.1 .______ Antrian
coroutine untuk berpartisipasi dalam perencanaan2.2.2 .______
Memulai fungsi panggil balik ( callback )2.2.3 .______
Catatan: coroutine sebagai metode terkait. Nilai-nilai pengembalian.2.3 .___ Penundaan
3. Sinkronisasi dan kelasnya3.1 .___ Kunci
Kunci3.1.1 .______
Kunci dan batas waktu3.2 .___
Acara3.2.1 .______
Nilai Acara
3.3 .___ Barrier
Barrier3.4 .___
Semaphore3.4.1 .______
Semaphore Terbatas3.5 .___ Antrian
Antrian3.6 .___ Kelas Sinkronisasi Lainnya
4. Pengembangan kelas untuk asyncio4.1 .___ Kelas menggunakan
menunggu4.1.1 .______
Gunakan dalam konteks manajer4.1.2 .______
Menunggu di coroutine4.2 .___ Iterator Asinkron
4.3 .___ Manajer Konteks Asinkron
5. Pengecualian untuk timeout dan karena pembatalan tugas5.1 .___ Pengecualian
5.2 .___ Pengecualian karena batas waktu dan karena pembatalan tugas
5.2.1 .______
Membatalkan tugas5.2.2 .______
Coroutine dengan batas waktu6. Interaksi dengan perangkat perangkat keras6.1 .___ Sinkronisasi Masalah
6.2 .___ Polling perangkat dengan coroutine
6.3 .___ Menggunakan mesin streaming
6.3.1 .______
Contoh Driver UART6.4 .___ Pengembangan Driver untuk Perangkat Streaming
6.5 .___ Contoh lengkap:
aremote.py Driver untuk penerima remote control IR.
6.6 .___ Driver untuk sensor suhu dan kelembaban HTU21D.
7. Kiat dan Trik7.1 .___ Program macet
7.2
.___ uasyncio menyimpan status
7.3 .___ Pengumpulan sampah
7.4 .___ Pengujian
7.5 .___ Kesalahan umum. Mungkin sulit ditemukan.
7.6 .___ Pemrograman menggunakan soket (
soket )
7.6.1 .______
Masalah WiFi7.7 .___ Argumen dari konstruktor loop acara
8. Catatan untuk pemula8.1 .___ Masalah 1: loop acara
8.2 .___ Masalah 2: metode penguncian
8.3
.___ Pendekatan
uasyncio8.4 .___ Perencanaan dalam
uasyncio8.5 .___ Mengapa kolaboratif, bukan penjadwalan berbasis
utas (
_thread )?
8.6 .___ Interaksi
8.7 .___
Polling0. PendahuluanSebagian besar dokumen ini mengasumsikan keakraban dengan pemrograman asinkron. Untuk pemula, pengantar dapat ditemukan di bagian 7.
Perpustakaan
uasyncio untuk
MicroPython termasuk subset dari perpustakaan
Python asyncio dan dimaksudkan untuk digunakan pada mikrokontroler. Karena itu, dibutuhkan sejumlah kecil RAM dan dikonfigurasi untuk dengan cepat beralih konteks dengan alokasi nol RAM.
Dokumen ini menjelaskan penggunaan
uasyncio dengan penekanan pada pembuatan driver untuk perangkat perangkat keras.
Tujuannya adalah untuk mendesain driver agar aplikasi terus bekerja sementara driver sedang menunggu respons dari perangkat. Pada saat yang sama, aplikasi tetap peka terhadap peristiwa lain dan interaksi pengguna.
Bidang penting lain dari aplikasi
asyncio adalah pemrograman jaringan: di Internet Anda dapat menemukan informasi yang cukup tentang topik ini.
Perhatikan bahwa
MicroPython didasarkan pada
Python 3.4 dengan add-ons minimum
Python 3.5 . Kecuali seperti yang dijelaskan di bawah ini, fungsi versi
asyncio yang lebih tua dari 3,4 tidak didukung. Dokumen ini mendefinisikan fitur-fitur yang didukung dalam subset ini.
Tujuan dari panduan ini adalah untuk memperkenalkan gaya pemrograman yang kompatibel dengan
CPython V3.5 dan lebih tinggi.
0,1 Instal uasyncio pada perangkat kosong (perangkat keras)Disarankan untuk menggunakan firmware
MicroPython V1.11 atau yang lebih baru. Pada banyak platform, instalasi tidak diperlukan, karena
uasyncio sudah dikompilasi dalam perakitan. Untuk memeriksanya, cukup ketik REPL
import uasyncio
Instruksi berikut mencakup kasus-kasus ketika modul tidak diinstal sebelumnya. Modul
antrian dan
sinkronisasi adalah opsional, tetapi diharuskan untuk menjalankan contoh yang diberikan di sini.
Perangkat yang terhubung internetPada perangkat yang terhubung ke Internet dan menjalankan firmware V1.11 atau yang lebih baru, Anda dapat menginstal menggunakan versi
upip bawaan . Memastikan perangkat terhubung ke jaringan Anda:
import upip upip.install ( 'micropython-uasyncio' ) upip.install ( 'micropython-uasyncio.synchro' ) upip.install ( 'micropython-uasyncio.queues' )
Pesan kesalahan dari
upip tidak terlalu berguna. Jika Anda mendapatkan kesalahan yang tidak dapat dipahami, periksa koneksi Internet lagi.
Perangkat keras tanpa koneksi internet ( mikropip )Jika perangkat
Anda tidak memiliki koneksi Internet (misalnya,
Pyboard V1.x ), cara termudah adalah memulai instalasi
micropip.py di komputer ke direktori pilihan Anda, lalu salin struktur direktori yang dihasilkan ke perangkat target. Utilitas
micropip.py berjalan pada
Python 3.2 atau yang lebih baru dan berjalan di Linux, Windows, dan OSX. Informasi lebih lanjut dapat ditemukan di
sini .
Panggilan biasa:
$ micropip.py install -p ~/rats micropython-uasyncio $ micropip.py install -p ~/rats micropython-uasyncio.synchro $ micropip.py install -p ~/rats micropython-uasyncio.queues
Perangkat tanpa koneksi internet (sumber penyalinan)Jika Anda tidak menggunakan
micropip.py , file harus disalin dari sumbernya. Petunjuk berikut menjelaskan cara menyalin jumlah minimum file ke perangkat target, serta kasus ketika
uasyncio perlu dikompresi ke dalam rakitan yang dikompilasi dalam bentuk bytecode untuk mengurangi ruang yang ditempati. Untuk versi terbaru yang kompatibel dengan firmware resmi, file harus disalin dari situs
web resmi
micropython-lib .
Kloning perpustakaan ke komputer dengan perintah
$ git clone https://github.com/micropython/micropython-lib.git
Pada perangkat target, buat direktori
uasyncio (opsional di direktori lib) dan salin file berikut ke dalamnya:
β’ uasyncio / uasyncio / __ init__.py
β’ uasyncio.core / uasyncio / core.py
β’ uasyncio.synchro / uasyncio / synchro.py
β’ uasyncio.queues / uasyncio / queues.py
Modul
uasyncio ini dapat dikompresi menjadi bytecode dengan menempatkan direktori
uasyncio dan isinya di port direktori
modules dan mengkompilasi ulang isinya.
1. Perencanaan bersamaTeknik eksekusi bersama dari beberapa tugas banyak digunakan dalam embedded system, yang menawarkan lebih sedikit overhead daripada penjadwalan threading (
_thread ), menghindari banyak jebakan yang terkait dengan utas yang benar-benar tidak sinkron.
1.1 ModulBerikut ini adalah daftar modul yang dapat berjalan di perangkat target.
Perpustakaan1.
asyn.py Menyediakan
Kunci, Peristiwa, Penghalang, Semaphore, BoundedSemaphore, Kondisi, kumpulkan primitif sinkronisasi. Memberikan dukungan untuk membatalkan tugas melalui kelas
NamedTask dan
Cancellable .
2.
aswitch.py Merupakan kelas untuk memasangkan sakelar dan tombol, serta objek program dengan kemungkinan penundaan berulang. Tombol adalah generalisasi dari sakelar yang menyediakan keadaan logis dan bukan fisik, serta peristiwa yang dipicu oleh pers ganda dan panjang.
Program demoDua yang pertama paling berguna karena mereka memberikan hasil yang terlihat ketika mengakses
perangkat keras Pyboard .
- aledflash.py Flashes empat indikator Pyboard secara sinkron selama 10 detik. Demonstrasi uasyncio yang paling sederhana. Impor untuk dijalankan.
- apoll.py Driver perangkat untuk accelerometer Pyboard . Menunjukkan penggunaan coroutine untuk permintaan perangkat. Bekerja selama 20 detik. Impor untuk dijalankan. Membutuhkan Pyboard V1.x.
- astests.py Program uji / demo untuk modul aswitch .
- asyn_demos.py Demo sederhana untuk membatalkan tugas.
- roundrobin.py Peragaan perencanaan melingkar. Juga menjadi tolok ukur untuk perencanaan kinerja.
- awaitable.py Demonstrasi kelas dengan menunggu. Salah satu cara untuk mengimplementasikan pengandar perangkat yang polling antarmuka.
- chain.py Disalin dari dokumentasi Python . Demonstrasi rantai coroutine.
- aqtest.py Demonstrasi kelas antrian perpustakaan uasyncio .
- aremote.py Contoh driver perangkat untuk protokol NEC IR.
- auart.py Peragaan streaming input-output melalui Pyboard UART .
- auart_hd.py Menggunakan Pyboard UART untuk berkomunikasi dengan perangkat menggunakan protokol setengah dupleks. Cocok untuk perangkat, misalnya, menggunakan set perintah modem AT.
- iorw.py Demonstrasi perangkat pembaca / penulis menggunakan streaming I / O.
Program uji- asyntest.py Tes untuk kelas sinkronisasi di asyn.py.
- cantest.py Tes pembatalan pekerjaan.
Utilitas1.
check_async_code.py Utilitas ini ditulis dalam
Python3 untuk mendeteksi kesalahan pengkodean tertentu yang sulit ditemukan. Lihat bagian 7.5.
KontrolDirektori
tolok ukur berisi skrip untuk memeriksa dan mengkarakterisasi
penjadwal uasyncio .
2. perpustakaan uasyncioKonsep
asyncio didasarkan pada organisasi perencanaan untuk pelaksanaan bersama beberapa tugas, yang dalam dokumen ini disebut
coroutine .
2.1 Struktur program: loop acaraPerhatikan contoh berikut:
import uasyncio as asyncio async def bar (): count = 0, while True : count + = 1 print ( count ) await asyncio.sleep ( 1 )
Eksekusi program berlanjut sampai
loop.run_forever dipanggil . Pada titik ini, eksekusi dikendalikan oleh penjadwal. Baris demi
loop.run_forever tidak akan pernah dieksekusi. Penjadwal mengeksekusi kode
batang karena telah di-antri di
scheduler loop.create_task . Dalam contoh sepele ini, hanya ada satu
batang coroutine. Jika ada yang lain, penjadwal akan menjalankannya selama periode ketika
bilah dijeda.
Sebagian besar aplikasi yang disematkan memiliki loop acara yang berkelanjutan. Perulangan peristiwa juga dapat dimulai dengan cara yang memungkinkan penyelesaian menggunakan
run_until_complete metode peristiwa; Ini terutama digunakan dalam pengujian. Contohnya dapat ditemukan di modul
astests.py .
Contoh loop peristiwa adalah objek tunggal yang dibuat oleh panggilan pertama ke
asyncio.get_event_loop () dengan dua argumen integer opsional yang menunjukkan jumlah coroutine dalam dua antrian - mulai dan menunggu. Biasanya, kedua argumen akan memiliki nilai yang sama, sama dengan setidaknya jumlah coroutine yang dieksekusi secara bersamaan dalam aplikasi. Biasanya, nilai default 16 sudah cukup. Jika nilai-nilai non-standar digunakan, lihat Argumen dari konstruktor loop peristiwa (bagian 7.7.).
Jika coroutine perlu memanggil metode event loop (biasanya
create_task ), memanggil
asyncio.get_event_loop () (tanpa argumen) akan mengembalikannya secara efektif.
2.2 CoroutineCoroutine dibuat sebagai berikut:
async def foo ( delay_secs ): await asyncio.sleep ( delay_secs ) print ( 'Hello' )
Coroutine dapat memungkinkan coroutine lainnya diluncurkan menggunakan pernyataan
tunggu . Coroutine harus mengandung setidaknya satu pernyataan
menunggu . Ini menyebabkan coroutine untuk mengeksekusi sebelum selesai, sebelum eksekusi berlanjut ke pernyataan berikutnya. Pertimbangkan sebuah contoh:
await asyncio.sleep ( delay_secs ) await asyncio.sleep ( 0 )
Baris pertama menyebabkan kode berhenti untuk waktu tunda, sementara coroutine lain menggunakan waktu ini untuk eksekusi mereka. Penundaan 0 menyebabkan semua coroutine yang tertunda dijalankan dalam urutan siklik hingga baris berikutnya dijalankan. Lihat contoh
roundrobin.py .
2.2.1. Antrian untuk merencanakan coroutine- EventLoop.create_task Argumen: Coroutine untuk dijalankan. Penjadwal mengantri coroutine untuk memulai sesegera mungkin. Panggilan create_task segera kembali. Coroutine dalam argumen ditentukan menggunakan sintaks pemanggilan fungsi dengan argumen yang diperlukan.
- EventLoop.run_until_complete Argument: Coroutine untuk dijalankan. Penjadwal mengantri coroutine untuk memulai sesegera mungkin. Coroutine dalam argumen ditentukan menggunakan sintaks pemanggilan fungsi dengan argumen yang diperlukan. Panggilan un_until_complete kembali ketika coroutine telah selesai: metode ini menyediakan cara untuk keluar dari penjadwal.
- menunggu Argumen: A coroutine untuk dijalankan, ditentukan menggunakan sintaks pemanggilan fungsi. Mulai coroutine sesegera mungkin. Coroutine yang tertunda diblokir sampai salah satu coroutine yang diharapkan selesai.
Di atas adalah kompatibel dengan
CPython . Metode
uasyncio tambahan dibahas dalam Catatan (Bagian 2.2.3.).
2.2.2 Memulai fungsi panggilan balikCallback haruslah fungsi
Python yang dirancang untuk dijalankan dalam waktu singkat. Hal ini disebabkan oleh fakta bahwa coroutine tidak akan dapat bekerja selama durasi dari pelaksanaan fungsi tersebut.
Metode kelas
EventLoop berikut menggunakan panggilan balik:
- call_soon - panggilan sesegera mungkin. Args: callback callback untuk dijalankan, * args argumen posisi apa pun dapat diikuti oleh koma.
- call_later - panggilan setelah penundaan dalam hitungan detik. Args: delay, callback, * args
- call_later_ms - panggilan setelah penundaan dalam ms. Args: delay, callback, * args .
loop = asyncio.get_event_loop () loop.call_soon ( foo , 5 )
2.2.3 CatatanCoroutine dapat berisi pernyataan
pengembalian dengan nilai pengembalian yang berubah-ubah. Untuk mendapatkan nilai ini:
result = await my_coro ()
Coroutine dapat dibatasi dengan metode dan harus mengandung setidaknya satu pernyataan
menunggu .
2.3 PenundaanAda dua opsi untuk mengatur keterlambatan dalam coroutine. Untuk penundaan yang lebih lama dan dalam kasus di mana durasinya tidak perlu akurat, Anda dapat menggunakan:
async def foo( delay_secs , delay_ms ): await asyncio.sleep ( delay_secs ) print ( 'Hello' ) await asyncio.sleep_ms ( delay_ms )
Selama penundaan tersebut, penjadwal akan menjalankan coroutine lain. Ini dapat menimbulkan ketidakpastian waktu, karena coroutine yang memanggil hanya akan diluncurkan ketika yang sedang dijalankan dijalankan. Jumlah penundaan tergantung pada pengembang aplikasi, tetapi mungkin akan berada di urutan puluhan atau ratusan ms; ini dibahas lebih lanjut dalam Interaksi dengan Perangkat Keras (Bagian 6).
Penundaan yang sangat tepat dapat dilakukan menggunakan fungsi
utime -
sleep_ms dan
sleep_us . Mereka paling cocok untuk penundaan pendek, karena penjadwal tidak akan dapat mengeksekusi coroutine lain saat penundaan sedang berlangsung.
3. SinkronisasiSeringkali ada kebutuhan untuk memastikan sinkronisasi antar coroutine. Contoh umum adalah untuk menghindari apa yang disebut "kondisi ras" ketika beberapa coroutine secara bersamaan membutuhkan akses ke sumber daya yang sama. Contoh disediakan di
astests.py dan dibahas dalam
dokumentasi . Bahaya lain adalah "pelukan maut," ketika masing-masing coroutine menunggu selesainya yang lain.
Dalam aplikasi sederhana, sinkronisasi dapat dicapai menggunakan flag global atau variabel terkait. Pendekatan yang lebih elegan adalah dengan menggunakan kelas sinkronisasi. Modul
asyn.py menawarkan implementasi "mikro" dari kelas
Event, Barrier, Semaphore, dan
Conditios , yang dimaksudkan untuk digunakan hanya dengan
asyncio . Mereka tidak berorientasi thread dan tidak boleh digunakan dengan modul
_thread atau interrupt handler, kecuali ditentukan lain. Kelas
Lock juga diterapkan, yang merupakan alternatif dari implementasi resmi.
Masalah sinkronisasi lain muncul dengan produsen coroutine dan konsumen coroutine. Produsen coroutine menghasilkan data yang digunakan konsumen coroutine. Untuk melakukan ini,
asyncio menyediakan kelas
Queue . Produsen coroutine menempatkan data dalam antrian, sementara konsumen coroutine sedang menunggu penyelesaiannya (dengan operasi lain dijadwalkan tepat waktu). Kelas
Antrian memberikan jaminan untuk menghapus item dalam urutan penerimaannya. Atau, Anda dapat menggunakan kelas
Barrier jika produsen coroutine harus menunggu sampai konsumen coroutine siap mengakses data.
Tinjauan singkat kelas diberikan di bawah ini. Lebih detail dalam
dokumentasi lengkap .
3.1 KunciKunci menjamin akses unik ke sumber daya bersama. Contoh kode berikut ini membuat turunan dari kelas
kunci Kunci yang diteruskan ke semua klien yang ingin mengakses sumber daya bersama. Setiap coroutine mencoba menangkap kunci, menjeda eksekusi sampai berhasil:
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.Kunci dan batas waktuPada saat penulisan (5 Januari 2018), pengembangan kelas
uasycio Lock belum selesai secara resmi. Jika coroutine memiliki
batas waktu (bagian 5.2.2.) , Ketika menunggu kunci ketika dipicu, batas waktu akan tidak efektif. Itu tidak akan menerima
TimeoutError sampai ia menerima kunci. Hal yang sama berlaku untuk membatalkan tugas.
Modul
asyn.py menawarkan kelas
Lock , yang berfungsi dalam situasi ini. Implementasi kelas ini kurang efisien daripada kelas resmi, tetapi mendukung antarmuka tambahan sesuai dengan versi
CPython , termasuk penggunaan manajer konteks.
3.2 AcaraPeristiwa menciptakan peluang bagi satu atau beberapa coroutine untuk berhenti, sementara yang lain memberi sinyal tentang kelanjutannya. Sebuah instance dari
Event menjadi tersedia untuk semua coroutine yang menggunakannya:
import asyn event = asyn.Event ()
Coroutine menunggu acara dengan mendeklarasikan
acara tunggu , setelah eksekusi berhenti sampai coroutine lain mendeklarasikan
event.set () .
Informasi lengkapMasalah dapat muncul jika
event.set () dikeluarkan dalam konstruksi perulangan; kode harus menunggu sampai semua objek yang tertunda memiliki akses ke acara sebelum mengaturnya lagi. Dalam kasus ketika satu
coro mengharapkan suatu peristiwa, ini dapat dicapai dengan menerima acara
coro membersihkan acara:
async def eventwait ( event ): await event event.clear()
Coroutine yang memicu acara memeriksa apakah telah dilayani:
async def foo ( event ): while True :
Dalam kasus ketika beberapa perusahaan sedang menunggu sinkronisasi satu peristiwa, masalahnya dapat diselesaikan dengan menggunakan acara konfirmasi. Setiap
coro membutuhkan acara terpisah.
async def eventwait ( , ack_event ): await event ack_event.set ()
Contoh dari ini diberikan dalam fungsi
event_test di
asyntest.py . Ini rumit dalam kebanyakan kasus - bahkan dengan satu menunggu
coro - kelas
Barrier , disajikan di bawah ini, menawarkan pendekatan yang lebih sederhana.
Suatu acara juga dapat menyediakan sarana komunikasi antara interrupt handler dan
coro . Pawang memelihara perangkat keras dan mengatur acara, yang diperiksa oleh
coro sudah dalam mode normal.
3.2.1 Nilai AcaraMetode
event.set () dapat mengambil nilai data opsional jenis apa pun.
Coro , menunggu acara, bisa mendapatkannya dengan
event.value () . Perhatikan bahwa
event.clear () akan diatur ke
Tidak Ada . Penggunaan khas ini untuk pengaturan
coro acara adalah untuk mengeluarkan
event.set (utime.ticks_ms ()) . Perusahaan apa pun yang menunggu acara dapat menentukan penundaan yang telah terjadi, misalnya, untuk mengkompensasi hal ini.
3.3 PenghalangAda dua kegunaan untuk kelas
Barrier .
Pertama, ia dapat menangguhkan coroutine sampai satu atau beberapa coroutine lainnya selesai.
Kedua, memungkinkan beberapa coroutine untuk bertemu pada titik tertentu. Misalnya, produsen dan konsumen dapat melakukan sinkronisasi pada titik di mana produsen memiliki data, dan konsumen siap menggunakannya. Pada saat eksekusi,
Penghalang dapat mengeluarkan panggilan balik tambahan sebelum penghalang dihapus dan semua peristiwa yang tertunda dapat dilanjutkan.
Callback dapat berupa fungsi atau coroutine. Dalam sebagian besar aplikasi, fungsi kemungkinan besar akan digunakan: itu dapat dijamin akan dieksekusi sebelum selesai, sebelum penghalang dihilangkan.
Contohnya adalah fungsi
barrier_test di
asyntest.py . Dalam cuplikan kode program ini:
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
beberapa contoh
laporan coroutine mencetak hasil mereka dan berhenti sampai contoh lainnya juga selesai dan menunggu
penghalang untuk melanjutkan. Pada titik ini, panggilan balik sedang dilakukan. Setelah selesai, coroutine asli dilanjutkan.
3.4 SemaphoreSemaphore membatasi jumlah coroutine yang dapat mengakses sumber daya. Dapat digunakan untuk membatasi jumlah instance dari coroutine tertentu yang dapat berjalan secara bersamaan. Ini dilakukan dengan menggunakan penghitung akses, yang diinisialisasi oleh konstruktor dan dikurangi setiap kali coroutine menerima semaphore.
Cara termudah untuk menggunakannya dalam manajer konteks:
import asyn sema = asyn.Semaphore(3) async def foo(sema): async with sema:
Contohnya adalah fungsi
semaphore_test di
asyntest.py .
3.4.1 ( Terbatas ) semaphoreIa bekerja mirip dengan kelas
Semaphore kecuali bahwa jika metode
rilis menyebabkan penghitung akses melebihi nilai awal,
ValueError diatur.
3,5 AntrianKelas
Antrian dikelola oleh
uasycio resmi dan program sampel
aqtest.py menunjukkan penggunaannya. Antrian dibuat sebagai berikut:
from uasyncio.queues import Queue q = Queue ()
Coroutine pabrikan yang khas dapat bekerja sebagai berikut:
async def producer(q): while True: result = await slow_process()
dan coroutine konsumen dapat berfungsi sebagai berikut:
async def consumer(q): while True: result = await(q.get())
Kelas
Antrian menyediakan fungsionalitas tambahan yang signifikan ketika ukuran antrian dapat dibatasi dan status dapat disurvei. Perilaku dengan antrian kosong (jika ukuran terbatas) dan perilaku dengan antrian penuh dapat dikontrol.
Dokumentasi tentang ini ada dalam kode.3.6 Kelas Sinkronisasi LainnyaPustaka asyn.py menyediakan implementasi mikro dari beberapa fitur CPython lainnya .Kelas Kondisi memungkinkan coroutine untuk memberi tahu coroutine lain yang menunggu pada sumber yang terkunci. Setelah menerima pemberitahuan, mereka akan mendapatkan akses ke sumber daya dan membuka kunci pada gilirannya. Pemberitahuan coroutine dapat membatasi jumlah coroutine yang akan diberitahukan.Kelas Kumpulkan memungkinkan Anda untuk menjalankan daftar coroutine. Setelah menyelesaikan yang terakhir, daftar hasil akan dikembalikan. Implementasi "mikro" ini menggunakan sintaks yang berbeda. Timeout dapat diterapkan ke salah satu coroutine.4 Mengembangkan kelas untuk asyncioDalam konteks pengembangan driver perangkat, tujuannya adalah untuk memastikan bahwa mereka bekerja tanpa pemblokiran. Driver coroutine harus memastikan bahwa coroutine lain dijalankan saat driver sedang menunggu perangkat melakukan operasi perangkat keras. Misalnya, tugas menunggu data tiba di UART, atau pengguna yang menekan tombol harus memungkinkan acara lain dijadwalkan hingga acara tersebut terjadi.4.1 Kelas menggunakan menunggu menunggu Sebuah coroutinedapat menjeda eksekusi sambil menunggu objek yang ditunggu . Di bawah CPython, kelas custom yang ditunggu dibuat dengan menerapkan metode __await__ khususdimana generator kembali. Kelas yang ditunggu digunakan sebagai berikut: import uasyncio as asyncio class Foo(): def __await__(self): for n in range(5): print('__await__ called') yield from asyncio.sleep(1)
Saat MicroPython tidak mendukung __await__ ( issue # 2678 ) dan untuk solusi yang akan digunakan __iter__ . String __iter__ = __await__ menyediakan portabilitas antara CPython dan MicroPython . contoh kode, lihat kelas Event, Barrier, Cancellable, Kondisi di asyn.py .4.1.1 Penggunaan dalam manajer konteksObjek yang diharapkan dapat digunakan dalam manajer konteks sinkron atau asinkron, menyediakan metode khusus yang diperlukan. Sintaks: with await awaitable as a:
Untuk mencapai ini, generator __await__ harus kembali sendiri . Ini diteruskan ke variabel apa pun dalam klausa as , dan juga memungkinkan metode khusus untuk bekerja. Lihat asyn.Condition dan asyntest.condition_test di mana kelas kondisi menggunakan menunggu dan dapat digunakan dalam manajer konteks sinkron.4.1.2 coroutine Tunggulah dibahasa Python membutuhkan __await__ adalah generator fungsi. Dalam MicroPython, generator dan coroutine identik, jadi solusinya adalah menggunakan hasil dari coro (args) .Tujuan dari panduan ini adalah untuk menawarkan kode yang portabel ke CPython 3.5 atau yang lebih baru. Dalam CPython, generator dan coroutine berbeda artinya. Dalam CPython, coroutine memiliki metode khusus __await__ yang diambil oleh generator. Ini portabel: up = False
Perhatikan bahwa __await__, hasil dari asyncio.sleep (1) diizinkan oleh CPython . Saya masih tidak mengerti bagaimana ini dicapai.4.2. Asynchronous iterators.Asynchronous iterators menyediakan sarana untuk mengembalikan urutan nilai yang terbatas atau tidak terbatas dan dapat digunakan sebagai sarana untuk mengambil elemen data sekuensial ketika mereka datang dari perangkat read-only. Iterator asinkron memanggil kode asinkron dalam metode selanjutnya . Kelas harus memenuhi persyaratan berikut:- Ini memiliki metode __aiter__ yang didefinisikan dalam async def dan mengembalikan iterator asynchronous.
- Ini memiliki metode __anext__ , yang itu sendiri adalah coroutine - yaitu, didefinisikan melalui async def dan mengandung setidaknya satu pernyataan menunggu . Untuk menghentikan iterasi, itu harus meningkatkan pengecualian StopAsyncIteration .
Nilai serial diambil menggunakan async untuk seperti yang ditunjukkan di bawah ini: 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 Manajer konteks asinkronKelas dapat dirancang untuk mendukung manajer konteks asinkron yang memiliki prosedur masuk dan keluar yang merupakan program bersama. Contohnya adalah Kelas Kunci yang dijelaskan di atas. Ini memiliki __aenter__ coroutine , yang secara logis diperlukan untuk operasi asinkron. Untuk mendukung protokol asinkron dari manajer konteks, metode __aexit__- nya juga harus berupa coroutine, yang dicapai dengan memasukkan menunggu asyncio.sleep (0) . Kelas-kelas tersebut dapat diakses dari dalam coroutine dengan sintaks berikut: async def bar ( lock ): async with lock: print ( Β« bar Β» )
Seperti halnya manajer konteks biasa, metode keluar dijamin dipanggil ketika manajer konteks menyelesaikan pekerjaannya, seperti biasa, dan melalui pengecualian. Untuk mencapai ini, metode khusus __aenter__ dan __aexit__ digunakan, yang harus didefinisikan sebagai coroutine yang menunggu coroutine lain atau objek yang bisa ditunggu . Contoh ini diambil dari kelas Lock : async def __aenter__(self): await self.acquire()
Jika async dengan berisi klausa sebagai variabel , variabel mendapatkan nilai yang dikembalikan oleh __aenter__ .Untuk memastikan perilaku yang benar, firmware harus V1.9.10 atau yang lebih baru.5. Pengecualian untuk batas waktu dan karena pembatalan tugas. Topik-topik ini terkait: uasyncio mencakup pembatalan tugas dan menerapkan batas waktu untuk suatu tugas, melemparkan pengecualian untuk tugas tersebut dengan cara khusus.5.1 PengecualianJika pengecualian terjadi di coroutine, itu harus diproses baik di coroutine ini atau di coroutine menunggu penyelesaiannya. Ini memastikan bahwa pengecualian tidak berlaku untuk penjadwal. Jika pengecualian terjadi, penjadwal akan berhenti bekerja dengan meneruskan pengecualian ke kode yang penjadwal dimulai. Oleh karena itu, untuk menghindari penjadwalan berhenti, coroutine yang diluncurkan dengan loop.create_task () harus menangkap setiap pengecualian di dalamnya.Menggunakan lemparan atau tutup untuk melempar pengecualian di coroutine tidak masuk akal. Ini menghancurkan uasyncio , menyebabkan coroutine untuk memulai dan mungkin keluar ketika masih dalam antrian eksekusi.Contoh di atas menggambarkan situasi ini. Jika dibiarkan bekerja sampai akhir, itu berfungsi seperti yang diharapkan. 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')
Namun, mengeluarkan interupsi keyboard menyebabkan pengecualian masuk ke loop acara. Ini karena eksekusi uasyncio.sleep dilewatkan ke loop acara. Oleh karena itu, aplikasi yang memerlukan kode yang jelas sebagai respons atas gangguan keyboard harus mendapatkan pengecualian di level loop acara.5.2 Membatalkan dan waktu habisSeperti disebutkan di atas, fungsi-fungsi ini bekerja dengan melemparkan pengecualian untuk tugas dengan cara khusus menggunakan metode MicroPython khusus coroutine pend_throw . Cara kerjanya tergantung pada versi. Dalam uasyncio v.2.0 resmi , pengecualian tidak diproses hingga tugas terjadwal berikutnya. Ini memaksakan penundaan jika tugas mengharapkan tidurinput-output Waktu habis bisa melampaui periode nominalnya. Tugas pembatalan tugas lainnya tidak dapat menentukan kapan pembatalan selesai.Saat ini ada solusi dan dua solusi.- Penanganan masalah : Pustaka asyn menyediakan cara menunggu tugas atau kelompok tugas untuk dibatalkan. Lihat Membatalkan pekerjaan (bagian 5.2.1.).
- Perpustakaan Paul Sokolovsky menyediakan uasyncio v2.4 , tetapi ini membutuhkan firmware Pycopy -nya .
- Fast_io perpustakaan uasyncio memecahkan masalah ini dalam Python (cara kurang elegan) dan berjalan firmware resmi.
Hirarki pengecualian yang digunakan di sini adalah Exception-CancelledError-TimeoutError .5.2.1 Membatalkan pekerjaanuasyncio menyediakan fungsi cancel (coro) . Ini berfungsi dengan melempar pengecualian untuk menggunakan coroutine pend_throw . Ini juga bekerja dengan coroutine bersarang. Penggunaannya adalah sebagai berikut: async def foo(): while True:
Jika contoh ini dijalankan di bawah uasyncio v2.0 , maka ketika bilah kembali membatalkan, itu tidak akan berlaku sampai foo terjadwal berikutnya dan penundaan hingga 10 detik dapat terjadi ketika foo dibatalkan . Sumber keterlambatan lain akan terjadi jika foo menunggu I / O. Di mana pun penundaan terjadi, bilah tidak akan dapat menentukan apakah foo telah dibatalkan. Itu penting dalam beberapa kasus penggunaan.Saat menggunakan perpustakaan Paul Sokolovsky atau fast_io, cukup menggunakan sleep (0): async def foo(): while True:
Ini juga akan berfungsi di uasyncio v2.0 jika foo (dan sembarang coroutine foo ) tidak pernah kembali tidur dan tidak menunggu I / O.Perilaku yang mungkin mengejutkan kecerobohan terjadi ketika coroutine yang dijalankan oleh create_task dan dalam mode siaga diharapkan untuk membatalkan . Pertimbangkan cuplikan ini: async def foo(): while True:
Ketika foo dibatalkan, itu dihapus dari antrian scheduler; karena tidak memiliki pernyataan kembali , prosedur pemanggilan foo_runner tidak pernah dilanjutkan. Disarankan agar Anda selalu menangkap pengecualian di ruang lingkup fungsi terluar untuk dibatalkan: async def foo(): try: while True: await asyncio.sleep(10) await my_coro except asyncio.CancelledError: return
Dalam hal ini, my_coro tidak perlu menangkap pengecualian, karena akan disebarkan ke saluran panggilan dan ditangkap di sana.Catatan
Dilarang menggunakan metode coroutine close atau throw ketika coroutine digunakan di luar scheduler. Ini merusak penjadwal, memaksa coroutine untuk mengeksekusi kode, bahkan jika itu tidak dijadwalkan. Ini mungkin memiliki konsekuensi yang tidak diinginkan.5.2.2 Coroutines dengan timeoutTimeout diimplementasikan menggunakan uasyncio metode .wait_for () dan .wait_for_ms () . Mereka masing-masing mengambil coroutine dan latency dalam detik atau ms, sebagai argumen. Jika batas waktu berakhir, TimeoutError akan dilemparkan ke coroutine menggunakan pend_throw. Pengecualian ini harus ditangkap oleh pengguna atau penelepon. Ini diperlukan karena alasan yang dijelaskan di atas: jika batas waktu berakhir, dibatalkan. Kecuali jika kesalahan ditangkap dan dikembalikan, satu-satunya cara penelepon dapat melanjutkan adalah menangkap pengecualian itu sendiri.Di mana pengecualian ditangkap oleh coroutine, saya memiliki kegagalan yang tidak jelas jika pengecualian tidak tertangkap di lingkup luar, seperti yang ditunjukkan di bawah ini: 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')
Atau, Anda dapat mencegat fungsi panggilan: 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())
Catatan untuk Uasyncio v2.0 .Ini tidak berlaku untuk perpustakaan Paul Sokolovsky atau fast_io .Jika coroutine mulai menunggu asyncio.sleep (t) , dengan t penundaan yang lama, coroutine tidak akan memulai kembali sampai t berakhir . Jika batas waktu telah berlalu sebelum tidur berakhir , TimeoutError akan terjadi ketika coroutine dimuat - yaitu ketika t berakhir . Secara real time dan dari perspektif penelepon, respons TimeoutError- nya akan tertunda.Jika ini penting untuk aplikasi, buat penundaan lama sambil menunggu yang pendek di loop. Coroutineasyn.sleep mendukung ini.6 Interaksi dengan peralatanDasar interaksi antara uasyncio dan peristiwa asinkron eksternal adalah polling. Perangkat keras yang membutuhkan respons cepat dapat menggunakan interupsi. Tetapi interaksi antara rutin interupsi (ISR) dan coroutine pengguna akan didasarkan pada jajak pendapat. Misalnya, ISR dapat memanggil Acara atau menetapkan bendera global, sementara coroutine menunggu hasil polling objek setiap kali permintaan dijadwalkan.Interogasi dapat dilakukan dengan dua cara, eksplisit atau implisit. Yang terakhir dilakukan menggunakan aliran I / Osebuah mekanisme, yang merupakan sistem yang dirancang untuk perangkat streaming seperti UART dan soket . Dalam jajak pendapat eksplisit paling sederhana, kode berikut ini dapat terdiri dari: async def poll_my_device(): global my_flag
Alih-alih bendera global, Anda bisa menggunakan variabel instance dari kelas Event atau instance dari kelas yang menggunakan menunggu . Survei eksplisit dibahas di bawah ini.Polling implisit terdiri dari mengembangkan driver yang akan bertindak sebagai perangkat I / O streaming, seperti UART atau soket I / O stream , yang melakukan polling pada perangkat menggunakan sistem Python select.poll : karena polling dilakukan dalam C, lebih cepat dan lebih efisien daripada jajak pendapat eksplisit. Penggunaan aliran I / O dibahas di bagian 6.3.Karena keefektifannya, polling implisit memberikan keuntungan bagi driver perangkat I / O tercepat: driver streaming dapat dibuat untuk banyak perangkat yang biasanya tidak dianggap sebagai perangkat streaming. Ini dibahas secara lebih rinci di bagian 6.4.6.1 Masalah SinkronisasiBaik jajak pendapat eksplisit dan implisit saat ini didasarkan pada perencanaan siklus. Misalkan I / O bekerja secara bersamaan dengan N custom coroutine, yang masing-masing berjalan dengan zero delay Ketika I / O dilayani, maka akan disurvei segera setelah semua operasi pengguna dijadwalkan. Perkiraan keterlambatan harus dipertimbangkan ketika merancang. Saluran I / O mungkin memerlukan penyanggaan, dengan peralatan servis ISR secara real time dari buffer dan coroutine, mengisi atau membebaskan buffer pada waktu yang lebih lambat.Penting juga untuk mempertimbangkan kemungkinan melampaui: inilah kasus ketika sesuatu yang diinterogasi oleh coroutine terjadi lebih dari sekali sebelum benar-benar direncanakan oleh coroutine.Masalah waktu lainnya adalah akurasi latensi. Jika masalah coroutine await asyncio.sleep_ms ( t )
penjadwal menjamin bahwa eksekusi akan ditangguhkan untuk setidaknya t ms. Penundaan yang sebenarnya mungkin lebih besar dari t, yang tergantung pada beban sistem saat ini. Jika saat ini coroutine lain sedang menunggu penyelesaian penundaan yang tidak nol, baris berikutnya akan segera dijadwalkan untuk dieksekusi. Tetapi jika coroutine lain juga menunggu eksekusi (baik karena mereka mengeluarkan penundaan nol, atau karena waktu mereka juga telah berakhir), mereka mungkin dijadwalkan untuk mengeksekusi lebih awal. Ini memperkenalkan ketidakpastian sinkronisasi ke fungsi sleep () dan sleep_ms () . Nilai kasus terburuk untuk limpahan ini dapat dihitung dengan menjumlahkan nilai runtime dari semua coroutine tersebut untuk menentukan waktu transmisi kasus terburuk ke penjadwal.Versi fast_io dari uasyncio dalam konteks ini menyediakan cara untuk memastikan bahwa streaming I / O akan disurvei pada setiap iterasi dari penjadwal. Diharapkan bahwa uasyncio resmi akan menerima amandemen yang relevan pada waktunya.6.2 Perangkat interogasi menggunakan coroutineIni adalah pendekatan sederhana yang paling cocok untuk perangkat yang dapat diinterogasi dengan kecepatan yang relatif rendah. Hal ini terutama disebabkan oleh fakta bahwa pemungutan suara dengan interval pemungutan suara pendek (atau nol) dapat mengarah pada fakta bahwa coroutine menghabiskan lebih banyak waktu prosesor daripada yang diinginkan untuk jatuh ke dalam interval.Contoh apoll.py menunjukkan pendekatan ini dengan menanyakan accelerometer Pyboarddengan interval 100 ms. Ia melakukan penyaringan sederhana untuk mengabaikan kebisingan, dan mencetak pesan setiap dua detik jika tidak ada gerakan yang terjadi.Contoh aswitch.py menyediakan driver untuk sakelar dan perangkat tombol.Contoh driver untuk perangkat yang mampu membaca dan menulis ditunjukkan di bawah ini. Untuk kemudahan pengujian, Pyboard UART 4 ββmengemulasi perangkat bersyarat. Driver mengimplementasikan RecordOrientedUart Class, di mana data disediakan dalam catatan panjang variabel yang terdiri dari instance byte. Objek menambahkan pembatas sebelum mengirim dan buffer data yang masuk sampai pembatas ditambahkan diterima. Ini hanya demo dan cara yang tidak efisien untuk menggunakan UART dibandingkan dengan streaming input / output.Untuk mendemonstrasikan transfer asinkron, kami mengasumsikan bahwa perangkat yang ditiru memiliki cara untuk memverifikasi bahwa transfer telah selesai dan bahwa aplikasi mengharuskan kami untuk menunggu. Tidak ada asumsi yang benar dalam contoh ini, tetapi kode memalsukannya dengan menunggu asyncio.sleep (0.1) .Untuk memulai, jangan lupa menghubungkan output dari Pyboard X1 dan X2 (UART Txd dan 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 Menggunakan Mekanisme Streaming ( Stream )Contoh menunjukkan input-output simultan pada satu UART dari mikroprosesor Pyboard .Untuk memulai, sambungkan output dari Pyboard X1 dan X2 (UART Txd dan 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()
Kode pendukung dapat ditemukan di __init__.py di perpustakaan uasyncio . Mekanisme ini berfungsi karena driver perangkat (ditulis dalam C ) mengimplementasikan metode berikut: ioctl, membaca, membaca dan menulis . Bagian 6.4: Menulis Driver Perangkat Streaming mengungkapkan rincian tentang bagaimana driver tersebut dapat ditulis dalam Python .UART dapat menerima data kapan saja. Mekanisme streaming I / O memeriksa karakter yang masuk tertunda setiap kali penjadwal mendapatkan kontrol. Ketika coroutine sedang berjalan, interrupt rutin menyangga karakter yang masuk; mereka akan dihapus ketika coroutine memberi jalan ke penjadwal. Oleh karena itu, aplikasi UART harus dirancang sedemikian rupa sehingga coroutine meminimalkan waktu antara transfer ke penjadwal untuk menghindari buffer overflows dan kehilangan data. Ini dapat ditingkatkan dengan menggunakan buffer baca UART yang lebih besar, atau kecepatan data yang lebih rendah. Atau, kontrol aliran perangkat keras akan memberikan solusi jika sumber data mendukungnya.6.3.1 Contoh UART pengemudiProgram auart_hd.pymenggambarkan metode komunikasi dengan perangkat setengah dupleks, seperti perangkat yang menanggapi set perintah modem AT. Half duplex berarti perangkat tidak pernah mengirim data yang tidak diminta: transfernya selalu dilakukan sebagai respons terhadap perintah yang diterima dari master.Perangkat ini ditiru dengan menjalankan tes pada Pyboard dengan dua koneksi kabel.Perangkat yang ditiru (sangat disederhanakan) merespons perintah apa pun dengan mengirim empat baris data dengan jeda di antara masing-masing untuk mensimulasikan pemrosesan yang lambat.Wisaya mengirim perintah, tetapi tidak tahu sebelumnya berapa banyak baris data yang akan dikembalikan. Ini memulai timer restart yang me-restart setiap kali garis diterima. Ketika penghitung waktu berakhir, diasumsikan bahwa perangkat telah menyelesaikan transmisi dan daftar saluran yang diterima dikembalikan.Kasus kegagalan perangkat juga ditunjukkan, yang dicapai dengan melewatkan transmisi sebelum menunggu tanggapan. Setelah batas waktu habis, daftar kosong dikembalikan. Lihat komentar kode untuk detail lebih lanjut.6.4 Pengembangan streaming (driver Streaming ) Unitinput stream / mekanisme keluaran ( streaming I / O ) untuk mengendalikan operasi streaming perangkat I / O seperti UART dan soket ( socket) Mekanisme ini dapat digunakan oleh driver dari setiap perangkat yang disurvei secara teratur dengan mendelegasikan ke penjadwal yang menggunakan pilih , polling kesiapan perangkat apa pun dalam antrian. Ini lebih efisien daripada melakukan beberapa operasi coroutine, yang masing-masing polling perangkat, sebagian karena pilih ditulis dalam C , dan juga karena coroutine yang melakukan polling ditunda sampai objek yang disurvei mengembalikan keadaan siap.Driver perangkat yang mampu memperbaiki mekanisme input / output streaming sebaiknya mendukung metode StreamReader, StreamWriter. Perangkat yang dapat dibaca harus menyediakan setidaknya satu dari metode berikut. Harap perhatikan bahwa ini adalah metode sinkron. Metode ioctl (lihat di bawah) memastikan bahwa mereka dipanggil hanya ketika data tersedia. Metode harus dikembalikan secepat mungkin, menggunakan sebanyak mungkin data yang tersedia.readline () Kembalikan karakter sebanyak mungkin, hingga karakter baris baru. Diperlukan jika menggunakan StreamReader.readline ()baca (n) Kembalikan karakter sebanyak mungkin, tetapi tidak lebih dari n . Diperlukan jika menggunakan StreamReader.read () atau StreamReader.readexactly ()Driver yang dibuat harus menyediakan metode sinkron berikut dengan pengembalian segera:tulis dengan argumen buf, off, sz .Di mana:buf adalah buffer untuk menulis.off - offset ke buffer karakter pertama yang ditulis.sz - jumlah karakter yang diminta untuk ditulis.Nilai kembali adalah jumlah karakter yang benar-benar ditulis (mungkin 1 jika perangkat lambat).Metode ioctl memastikan bahwa itu hanya akan dipanggil ketika perangkat siap menerima data.Semua perangkat harus menyediakan metode ioctl yang mensurvei peralatan untuk menentukan status ketersediaannya. Contoh khas untuk driver baca / tulis: 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):
Berikut ini adalah deskripsi penundaan tunggu Kelas 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
yang bisa digunakan sebagai berikut: async def timer_test ( n ): timer = ms_timer.MillisecTimer () await timer ( 30 )
Dibandingkan dengan uasyncio resmi , implementasi seperti itu tidak menawarkan keuntungan apa pun dibandingkan dengan menunggu asyncio.sleep_ms () . Penggunaan fast_io memberikan penundaan yang jauh lebih akurat dalam pola penggunaan normal, ketika coroutine mengharapkan zero delay.Anda dapat menggunakan penjadwalan I / O untuk mengaitkan suatu peristiwa dengan panggilan balik. Ini lebih efisien daripada siklus pemungutan suara, karena pemungutan suara tidak dijadwalkan sampai ioctl kembali siap. Selanjutnya, panggilan balik dilakukan ketika panggilan balik berubah status. 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
Dan lagi - pada uasyncio resmi , penundaan mungkin tinggi. Bergantung pada desain aplikasi, versi fast_io mungkin lebih efisien.Program demonstrasi iorw.py menunjukkan contoh lengkap. Harap dicatat bahwa pada saat penulisan artikel di uasyncio resmi ada kesalahan karena ini tidak berfungsi . Ada dua solusi. Solusinya adalah menulis dua driver terpisah, satu untuk hanya baca dan satu untuk hanya menulis. Yang kedua adalah menggunakan fast_io , yang memecahkan masalah ini.Dalam uasyncio resmi , input / output sangat jarang direncanakan .6.5 Contoh lengkap: aremote.pyPengemudi dirancang untuk menerima / mendekode sinyal dari kendali jarak jauh inframerah. Driver aremote.py itu sendiri . Catatan berikut ini penting untuk penggunaan asyncio .Interupsi pada kontak mencatat waktu perubahan status (dalam mikrodetik) dan mengatur acara, melewatkan waktu ketika perubahan status pertama kali terjadi. Coroutine menunggu suatu peristiwa, melaporkan durasi paket data, kemudian menerjemahkan data yang tersimpan sebelum memanggil panggilan balik yang ditentukan oleh pengguna.Melewati waktu ke instance Event memungkinkan coroutine untuk mengkompensasiketerlambatan asyncio saat mengatur periode penundaan.6.6 sensor lingkungan HTU21DSopir HTU21D Chip memberikan pengukuran yang akurat dari suhu dan kelembaban.Chip membutuhkan sekitar 120 ms untuk menerima kedua elemen data. Pengemudi bekerja secara serempak, memprakarsai tanda terima dan penggunaan menunggu asyncio.sleep (t) sebelum membaca data, memperbarui variabel suhu dan kelembaban, yang dapat diakses kapan saja, yang memungkinkan coroutine lain diluncurkan saat driver chip berjalan.7. Tip dan Trik7.1 Program dibekukan Pembekuanbiasanya terjadi karena tugas diblokir tanpa konsesi: ini akan menyebabkan pembekuan seluruh sistem. Saat berkembang, ada baiknya memiliki coroutine yang secara berkala menyalakan LED bawaan. Ini memberikan konfirmasi bahwa penjadwal masih berjalan.7.2 uasyncio save stateSaat memulai program menggunakan uasyncio di REPL, lakukan soft reset (ctrl-D) di antara awal. Karena fakta bahwa uasyncio mempertahankan status di antara permulaan, perilaku yang tidak terduga dapat terjadi pada permulaan berikutnya.7.3 Pengumpulan sampahAnda dapat menjalankan coroutine dengan menentukan impor gc terlebih dahulu : gc.collect () gc.treshold ( gc.mem_free () // 4 + gc.mem_alloc ())
Tujuan dari ini dibahas di sini di bagian tumpukan.7.4 PengujianDianjurkan untuk memastikan bahwa driver perangkat mempertahankan kontrol bila perlu, yang dapat dilakukan dengan menjalankan satu atau lebih salinan korout fiktif yang memulai siklus pencetakan pesan dan memeriksa apakah itu berjalan selama periode ketika pengemudi dalam mode siaga: async def rr(n): while True: print('Roundrobin ', n) await asyncio.sleep(0)
Sebagai contoh jenis bahaya yang mungkin timbul, dalam contoh di atas, metode RecordOrientedUart __await__ pada awalnya ditulis sebagai: 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
Akibatnya, eksekusi diperluas hingga seluruh catatan diterima, serta fakta bahwa uart.any () selalu mengembalikan jumlah karakter yang bukan nol yang diterima. Pada saat panggilan, semua karakter mungkin telah diterima. Situasi ini dapat diatasi menggunakan loop eksternal: def __await__(self): data = b'' while not data.endswith(self.DELIMITER): yield from asyncio.sleep(0)
Mungkin perlu dicatat bahwa kesalahan ini tidak akan jelas jika data telah dikirim ke UART dengan kecepatan lebih rendah, daripada menggunakan tes umpan balik. Selamat datang di kegembiraan pemrograman waktu-nyata.7.5 Common errorJika suatu fungsi atau metode didefinisikan oleh async def dan selanjutnya dipanggil seolah-olah itu adalah panggilan biasa (sinkron), MicroPython tidak menampilkan pesan kesalahan. Ini dengan desain. Biasanya ini mengarah pada fakta bahwa program diam-diam tidak berfungsi dengan benar: async def foo():
Saya punya saran yang menyarankan untuk memperbaiki situasi di opsi 1 menggunakan fast_io .Modul check_async_code.py mencoba mendeteksi kasus penggunaan coroutine yang meragukan. Itu ditulis dalam Python3 dan dirancang untuk bekerja pada PC. Digunakan dalam skrip yang ditulis sesuai dengan pedoman yang diuraikan dalam panduan ini dengan coroutine yang dinyatakan menggunakan async def . Modul ini mengambil satu argumen, jalur ke file sumber MicroPython (atau --help).Harap dicatat bahwa itu agak kasar dan dimaksudkan untuk digunakan dalam file yang benar secara sintaksis, yang tidak dimulai secara default. Gunakan alat, seperti pylint, untuk memeriksa sintaksis umum ( pylint saat ini tidak memiliki kesalahan ini).Script menghasilkan positif palsu. Menurut rencana, coroutine adalah objek dari tingkat pertama, mereka dapat ditransfer ke fungsi dan disimpan dalam struktur data. Bergantung pada logika program, Anda dapat menyimpan fungsi atau hasil pelaksanaannya. Script tidak dapat menentukan tujuannya. Ini bertujuan untuk mengabaikan kasus yang tampaknya benar ketika mengidentifikasi kasus lain untuk dipertimbangkan. Misalkan foo di mana coroutine dinyatakan sebagai async def : loop.run_until_complete(foo())
Saya merasa ini berguna, tetapi perbaikan selalu diterima.7,6 Programming dengan soket ( soket )Ada dua pendekatan dasar untuk pemrograman soket uasyncio . Secara default, soket diblokir hingga operasi baca atau tulis yang ditentukan selesai. Uasyncio mendukung penguncian soket menggunakan select.poll untuk mencegah penjadwal memblokirnya. Dalam kebanyakan kasus, mekanisme ini paling mudah digunakan. Contoh kode klien dan server dapat ditemukan di direktori client_server . Userver menggunakan aplikasi select.poll dengan polling secara eksplisit soket server.Soket klien menggunakannya secara implisit dalam arti bahwa mesin streaming uasyncio menggunakannya secara langsung.Harap perhatikan bahwa socket.getaddrinfo saat ini diblokir. Waktu dalam kode sampel akan minimal, tetapi jika pencarian DNS diperlukan, periode pemblokiran bisa signifikan.Pendekatan kedua untuk pemrograman soket adalah dengan menggunakan soket yang tidak menghalangi. Ini menambah kompleksitas, tetapi perlu di beberapa aplikasi, terutama jika koneksi melalui WiFi (lihat di bawah).Pada saat penulisan (Maret 2019), dukungan TLS untuk soket yang tidak menghalangi sedang dikembangkan. Status pastinya tidak diketahui (untuk saya).Menggunakan soket yang tidak menghalangi membutuhkan perhatian terhadap detail. Jika pembacaan non-pemblokiran terjadi karena latensi server, tidak ada jaminan bahwa semua (atau apa saja) dari data yang diminta akan dikembalikan. Demikian pula, entri mungkin tidak selesai.Oleh karena itu, metode baca dan tulis asinkron harus secara iteratif melakukan operasi non-pemblokiran hingga data yang diperlukan dibaca atau ditulis. Dalam praktiknya, batas waktu mungkin diperlukan untuk menangani pemadaman server.Komplikasi lain adalah bahwa port ESP32 memiliki masalah yang memerlukan pembobolan yang cukup jahat untuk operasi bebas kesalahan. Saya belum menguji apakah ini masih terjadi.Modul Sock_nonblock.pymenggambarkan metode yang diperlukan. Ini bukan demo yang berfungsi dan keputusan cenderung bergantung pada aplikasi.7.6.1 Masalah dengan WiFiMekanisme streaming uasyncio bukan pilihan terbaik saat mendeteksi pemadaman WiFi. Saya merasa perlu untuk menggunakan soket non-pemblokiran untuk menyediakan operasi gagal-aman dan menghubungkan kembali klien jika terjadi kegagalan. Dokumenini menjelaskan masalah yang saya temui dalam aplikasi WiFi yang membuat soket tetap terbuka untuk waktu yang lama dan menguraikan solusinya. Pltcmmenawarkan klien MQTT asinkron yang kuat yang memberikan integritas pesan selama kegagalan WiFi. Tautan serial dupleks-penuh asinkron sederhana antara klien nirkabel dan server berkabel dengan pengiriman pesan yang terjamin dijelaskan.7.7 Argumen konstruktor loop peristiwaSebuah kesalahan kecil dapat terjadi jika Anda perlu membuat loop acara dengan nilai yang berbeda dari nilai default. Perulangan semacam itu harus dideklarasikan sebelum menjalankan kode lain menggunakan asyncio karena nilai-nilai ini mungkin diperlukan dalam kode ini. Jika tidak, kode akan diinisialisasi dengan nilai default: import uasyncio as asyncio import some_module bar = some_module.Bar()
Mengingat bahwa mengimpor modul dapat mengeksekusi kode, cara paling aman adalah dengan instantiate loop peristiwa segera setelah mengimpor uasyncio . import uasyncio as asyncio loop = asyncio.get_event_loop(runq_len=40, waitq_len=40) import some_module bar = some_module.Bar()
Saat menulis modul untuk digunakan oleh program lain, saya lebih suka menghindari menjalankan kode uasyncio setelah impor. Tulis fungsi dan metode untuk menunggu loop peristiwa sebagai argumen. Kemudian pastikan bahwa hanya aplikasi tingkat atas yang memanggil get_event_loop : import uasyncio as asyncio import my_module
Masalah ini dibahas di sini .8 catatan untuk pemulaCatatan ini dimaksudkan untuk pemula dalam kode asinkron dan mulai dengan deskripsi masalah yang coba dipecahkan oleh perencana, serta memberikan gambaran umum tentang pendekatan uasyncio untuk solusi.Bagian 8.5 membahas manfaat relatif modul uasyncio dan _ thread , serta mengapa Anda lebih suka menggunakan uasyncio coroutine dengan penjadwalan proaktif (_thread).8.1 Masalah 1: loop acaraAplikasi firmware tipikal bekerja terus-menerus dan pada saat yang sama harus menanggapi peristiwa eksternal, yang mungkin termasuk perubahan tegangan pada ADC, tampilan gangguan perangkat keras, atau simbol yang diterima di UART, atau data yang tersedia pada soket. Peristiwa ini terjadi secara tidak sinkron, dan kode harus dapat merespons terlepas dari urutan terjadinya. Selain itu, tugas yang tergantung waktu mungkin diperlukan, seperti menginstal LED.Cara yang jelas untuk melakukan ini adalah dengan loop acara uasycio . Contoh ini bukan kode praktis, tetapi berfungsi untuk menggambarkan bentuk umum dari loop peristiwa. def event_loop(): led_1_time = 0 led_1_period = 20 led_2_time = 0 led_2_period = 30 switch_state = switch.state()
Perulangan seperti itu berfungsi untuk contoh sederhana, tetapi karena jumlah kejadian bertambah, kode dengan cepat menjadi rumit. Mereka juga melanggar prinsip-prinsip pemrograman berorientasi objek dengan menggabungkan sebagian besar logika program di satu tempat, daripada menghubungkan kode ke objek yang dikendalikan. Kami ingin mengembangkan kelas untuk LED berkedip yang dapat dimasukkan ke modul dan diimpor. Pendekatan OOP untuk LED berkedip mungkin terlihat seperti ini: 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()
Penjadwal di uasyncio memungkinkan Anda membuat kelas-kelas seperti itu.8.2 Masalah 2: Metode PemblokiranMisalkan Anda perlu membaca sejumlah byte dari soket. Jika Anda memanggil socket.read (n) dengan socket blocking secara default, itu akan βblockβ (artinya, ia tidak akan dapat berhenti) sampai n byte diterima . Selama periode ini, aplikasi tidak akan menanggapi acara lainnya.Menggunakan soket uasyncio yang tidak menghalangi , Anda dapat menulis metode baca asinkron. Tugas yang membutuhkan data akan (harus) diblokir sampai diterima, tetapi tugas lain akan dilakukan selama periode ini, yang akan memungkinkan aplikasi tetap responsif.8.3. Pendekatan UasyncioKelas berikutnya memiliki LED yang dapat dinyalakan dan dimatikan, dan Anda juga dapat berkedip dengan kecepatan apa pun. Contoh LED_async menggunakan metode jalankan , yang dapat digunakan untuk operasi berkelanjutan. Perilaku LED dapat dikontrol menggunakan metode hidup (), mati (), dan lampu kilat (detik) . 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
Perlu dicatat bahwa on (), off () dan flash () adalah metode sinkron biasa. Mereka mengubah perilaku LED, tetapi segera kembali. Berkedip terjadi "di latar belakang." Ini dijelaskan secara rinci di bagian selanjutnya.Kelas mematuhi prinsip OOP, yang terdiri dari penyimpanan logika yang terkait dengan perangkat di kelas. Pada saat yang sama, penggunaan uasyncio memastikan bahwa aplikasi dapat merespons peristiwa lain saat LED berkedip. Program di bawah ini berkedip dengan empat LED Pyboard pada frekuensi yang berbeda, dan juga merespons tombol USR, yang melengkapinya. import pyb import uasyncio as asyncio from led_async import LED_async
Berbeda dengan contoh pertama dari sebuah loop peristiwa, logika yang terkait dengan sakelar berada dalam fungsi yang terpisah dari fungsi LED. Perhatikan kode yang digunakan untuk memulai penjadwal: loop = asyncio.get_event_loop() loop.run_until_complete(killer())
8.4 Perencanaan dalam uasyncioPython 3.5 dan MicroPython mendukung konsep fungsi asinkron, juga dikenal sebagai coroutine atau tugas. Coroutine harus menyertakan setidaknya satu pernyataan menunggu . async def hello(): for _ in range(10): print('Hello world.') await asyncio.sleep(1)
Fungsi ini mencetak pesan sepuluh kali dalam interval satu detik. Sementara fungsi dijeda untuk mengantisipasi penundaan, penjadwal asyncio akan melakukan tugas-tugas lain, menciptakan ilusi menjalankannya secara bersamaan.Ketika masalah coroutine menunggu asyncio.sleep_ms () atau menunggu asyncio.sleep (), tugas saat ini dijeda dan ditempatkan dalam antrian yang dipesan oleh waktu dan eksekusi dilanjutkan ke tugas di bagian atas antrian. Antrian dirancang sedemikian rupa sehingga, bahkan jika mode tidur yang ditentukan adalah nol, tugas-tugas relevan lainnya akan dilakukan sampai kembali saat ini. Ini adalah perencanaan "bundar jujur". Ini adalah praktik umum untuk menjalankan menunggu loop asyncio.sleep (0) .sehingga tugas tidak menunda eksekusi. Berikut ini adalah loop sibuk-tunggu menunggu tugas lain untuk mengatur variabel flag global . Sayangnya, itu memonopoli prosesor, mencegah peluncuran coroutine lain: async def bad_code(): global flag while not flag: pass
Masalahnya di sini adalah bahwa sampai flagis False loop melewati kontrol ke penjadwal, jadi tidak ada tugas lain yang akan dimulai. Pendekatan yang tepat: async def good_code(): global flag while not flag: await asyncio.sleep(0)
Untuk alasan yang sama, itu adalah praktik yang buruk untuk mengatur penundaan, misalnya, utime.sleep (1) karena memblokir tugas-tugas lain selama 1 detik; lebih tepat menggunakan menunggu asyncio.sleep (1) .Harap perhatikan bahwa keterlambatan yang dihasilkan oleh metode uasyncio sleep dan sleep_ms mungkin benar-benar melebihi waktu yang ditentukan. Ini disebabkan oleh kenyataan bahwa tugas-tugas lain akan dilakukan selama penundaan. Setelah periode penundaan berlalu, eksekusi tidak akan dilanjutkan sampai masalah tugas yang sedang berjalan menunggu atau berakhir. Coroutine yang berperilaku baik akan selalu menyatakan menunggusecara berkala. Di mana penundaan yang tepat diperlukan, terutama jika satu kurang dari beberapa ms, mungkin diperlukan untuk menggunakan utime.sleep_us (kami) .8.5 Mengapa kolaboratif, bukan penjadwalan berbasis utas ( _thread )?Reaksi awal pemula terhadap gagasan co-planning coroutine seringkali mengecewakan. Tentunya perencanaan streaming lebih baik? Mengapa saya harus secara eksplisit memberikan kontrol cara jika mesin virtual Python dapat melakukan ini untuk saya?Ketika datang ke sistem embedded, model kolaborasi memiliki dua keunggulan.Yang pertama adalah ringan. Dimungkinkan untuk memiliki sejumlah besar coroutine, karena tidak seperti utas terjadwal, coroutine yang ditangguhkan membutuhkan lebih sedikit ruang.Kedua, ini menghindari beberapa masalah halus terkait dengan penjadwalan streaming.Dalam praktiknya, multitasking kolaboratif banyak digunakan, terutama dalam aplikasi antarmuka pengguna.Untuk mempertahankan model perencanaan streaming, saya akan menunjukkan satu keuntungan: jika seseorang menulis for x in range ( 1000000 ):
itu tidak akan memblokir tugas lain. Model kolaborasi mengasumsikan bahwa loop harus secara eksplisit memberikan kontrol setiap tugas sejumlah iterasi, misalnya, menempatkan kode dalam coroutine dan mengeluarkan secara berkala menunggu asyncio.sleep (0) .Sayangnya, keuntungan ini tidak ada artinya dibandingkan dengan kerugiannya. Beberapa di antaranya dijelaskan dalam dokumentasi untuk menulis penangan interrupt.. Dalam model penjadwalan streaming, setiap utas dapat mengganggu utas lainnya, mengubah data yang dapat digunakan pada utas lainnya. Sebagai aturan, jauh lebih mudah untuk menemukan dan memperbaiki kunci yang terjadi karena kesalahan yang tidak memberikan hasil dibandingkan dengan pendeteksian kesalahan yang terkadang sangat halus dan jarang ditemui yang dapat terjadi dalam kode yang ditulis dalam kerangka model dengan perencanaan streaming.Sederhananya, jika Anda menulis coroutine MicroPython , Anda dapat yakin bahwa variabel tidak akan tiba-tiba diubah oleh coroutine lain: coroutine Anda memiliki kontrol penuh sampai kembali menunggu asyncio.sleep (0) .Perlu diingat bahwa interrupt handler adalah preemptive. Ini berlaku untuk gangguan perangkat keras dan lunak yang dapat terjadi di mana saja dalam kode Anda.Diskusi fasih tentang masalah perencanaan streaming dapat ditemukan di sini .8.6 InteraksiDalam aplikasi non-sepele, coroutine harus berinteraksi. Metode Python konvensional dapat digunakan . Ini termasuk menggunakan variabel global atau mendeklarasikan coroutine sebagai metode objek: mereka dapat berbagi variabel instan. Atau, objek yang bisa berubah dapat dilewatkan sebagai argumen ke coroutine.Model perencanaan streaming memerlukan spesialis untuk memastikan bahwa kelas menyediakan koneksi yang aman; dalam model kolaborasi, ini jarang diperlukan.8.7. Poll ( Polling )Beberapa perangkat keras seperti accelerometer Pyboard , tidak mendukung interupsi, dan karena itu harus disurvei (yaitu berkala diperiksa). Polling juga dapat digunakan bersama dengan penangan interrupt: penangan interrupt memelihara peralatan dan menetapkan bendera. Coroutine akan meng-polling flag - jika sudah diset, data diproses dan flag di-reset. Pendekatan terbaik adalah dengan menggunakan kelas Event .