Menggunakan asyncio untuk membuat driver perangkat asinkron di MicroPython v.1.12

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.

Isi
0. Pendahuluan
0.1 .___ Menginstal uasyncio pada perangkat kosong (perangkat keras)
1. Merencanakan pelaksanaan program bersama
1.1 .___ Modul
2. perpustakaan uasyncio
2.1 .___ Struktur Program: Siklus Pemrosesan Acara
2.2 .___ Coroutines
2.2.1 .______ Antrian coroutine untuk berpartisipasi dalam perencanaan
2.2.2 .______ Memulai fungsi panggil balik ( callback )
2.2.3 .______ Catatan: coroutine sebagai metode terkait. Nilai-nilai pengembalian.
2.3 .___ Penundaan
3. Sinkronisasi dan kelasnya
3.1 .___ Kunci Kunci
3.1.1 .______ Kunci dan batas waktu
3.2 .___ Acara
3.2.1 .______ Nilai Acara
3.3 .___ Barrier Barrier
3.4 .___ Semaphore
3.4.1 .______ Semaphore Terbatas
3.5 .___ Antrian Antrian
3.6 .___ Kelas Sinkronisasi Lainnya
4. Pengembangan kelas untuk asyncio
4.1 .___ Kelas menggunakan menunggu
4.1.1 .______ Gunakan dalam konteks manajer
4.1.2 .______ Menunggu di coroutine
4.2 .___ Iterator Asinkron
4.3 .___ Manajer Konteks Asinkron
5. Pengecualian untuk timeout dan karena pembatalan tugas
5.1 .___ Pengecualian
5.2 .___ Pengecualian karena batas waktu dan karena pembatalan tugas
5.2.1 .______ Membatalkan tugas
5.2.2 .______ Coroutine dengan batas waktu
6. Interaksi dengan perangkat perangkat keras
6.1 .___ Sinkronisasi Masalah
6.2 .___ Polling perangkat dengan coroutine
6.3 .___ Menggunakan mesin streaming
6.3.1 .______ Contoh Driver UART
6.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 Trik
7.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 WiFi
7.7 .___ Argumen dari konstruktor loop acara
8. Catatan untuk pemula
8.1 .___ Masalah 1: loop acara
8.2 .___ Masalah 2: metode penguncian
8.3 .___ Pendekatan uasyncio
8.4 .___ Perencanaan dalam uasyncio
8.5 .___ Mengapa kolaboratif, bukan penjadwalan berbasis utas ( _thread )?
8.6 .___ Interaksi
8.7 .___ Polling

0. Pendahuluan

Sebagian 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 internet

Pada 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 bersama

Teknik 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 Modul

Berikut ini adalah daftar modul yang dapat berjalan di perangkat target.

Perpustakaan

1. 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 demo

Dua yang pertama paling berguna karena mereka memberikan hasil yang terlihat ketika mengakses perangkat keras Pyboard .

  1. aledflash.py Flashes empat indikator Pyboard secara sinkron selama 10 detik. Demonstrasi uasyncio yang paling sederhana. Impor untuk dijalankan.
  2. apoll.py Driver perangkat untuk accelerometer Pyboard . Menunjukkan penggunaan coroutine untuk permintaan perangkat. Bekerja selama 20 detik. Impor untuk dijalankan. Membutuhkan Pyboard V1.x.
  3. astests.py Program uji / demo untuk modul aswitch .
  4. asyn_demos.py Demo sederhana untuk membatalkan tugas.
  5. roundrobin.py Peragaan perencanaan melingkar. Juga menjadi tolok ukur untuk perencanaan kinerja.
  6. awaitable.py Demonstrasi kelas dengan menunggu. Salah satu cara untuk mengimplementasikan pengandar perangkat yang polling antarmuka.
  7. chain.py Disalin dari dokumentasi Python . Demonstrasi rantai coroutine.
  8. aqtest.py Demonstrasi kelas antrian perpustakaan uasyncio .
  9. aremote.py Contoh driver perangkat untuk protokol NEC IR.
  10. auart.py Peragaan streaming input-output melalui Pyboard UART .
  11. auart_hd.py Menggunakan Pyboard UART untuk berkomunikasi dengan perangkat menggunakan protokol setengah dupleks. Cocok untuk perangkat, misalnya, menggunakan set perintah modem AT.
  12. iorw.py Demonstrasi perangkat pembaca / penulis menggunakan streaming I / O.

Program uji

  1. asyntest.py Tes untuk kelas sinkronisasi di asyn.py.
  2. cantest.py Tes pembatalan pekerjaan.

Utilitas

1. check_async_code.py Utilitas ini ditulis dalam Python3 untuk mendeteksi kesalahan pengkodean tertentu yang sulit ditemukan. Lihat bagian 7.5.

Kontrol

Direktori tolok ukur berisi skrip untuk memeriksa dan mengkarakterisasi penjadwal uasyncio .


2. perpustakaan uasyncio

Konsep asyncio didasarkan pada organisasi perencanaan untuk pelaksanaan bersama beberapa tugas, yang dalam dokumen ini disebut coroutine .

2.1 Struktur program: loop acara

Perhatikan contoh berikut:

 import uasyncio as asyncio async def bar (): count = 0, while True : count + = 1 print ( count ) await asyncio.sleep ( 1 ) #  1 loop = asyncio.get_event_loop () loop.create_task ( bar ()) #     loop.run_forever () 

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 Coroutine

Coroutine 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 balik

Callback 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:

  1. call_soon - panggilan sesegera mungkin. Args: callback callback untuk dijalankan, * args argumen posisi apa pun dapat diikuti oleh koma.
  2. call_later - panggilan setelah penundaan dalam hitungan detik. Args: delay, callback, * args
  3. call_later_ms - panggilan setelah penundaan dalam ms. Args: delay, callback, * args .

 loop = asyncio.get_event_loop () loop.call_soon ( foo , 5 ) #    'foo'      5. loop.call_later ( 2 , foo , 5 ) #   2 . loop.call_later_ms ( 50 , foo , 5 ) #   50 . loop.run_forever () 

2.2.3 Catatan

Coroutine 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 Penundaan

Ada 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. Sinkronisasi

Seringkali 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 Kunci

Kunci 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() # The global Lock instance loop.create_task(task(1, lock)) loop.create_task(task(2, lock)) loop.create_task(task(3, lock)) loop.run_until_complete(killer()) #  10s 

3.1.1.Kunci dan batas waktu

Pada 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 Acara

Peristiwa 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 lengkap

Masalah 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 : #   - while event.is_set (): await asyncio.sleep ( 1 ) # ,  coro   event.set () 

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 Acara

Metode 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 Penghalang

Ada 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 Semaphore

Semaphore 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 ) semaphore

Ia bekerja mirip dengan kelas Semaphore kecuali bahwa jika metode rilis menyebabkan penghitung akses melebihi nilai awal, ValueError diatur.

3,5 Antrian

Kelas 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() #       await q.put(result) #  ,        

dan coroutine konsumen dapat berfungsi sebagai berikut:

 async def consumer(q): while True: result = await(q.get()) # ,  q  print('Result was {}'.format(result)) 

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 Lainnya

Pustaka 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 asyncio

Dalam 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 coroutine

dapat 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) #     return 42 __iter__ = __await__ # .   async def bar(): foo = Foo() # Foo - awaitable  print('waiting for foo') res = await foo #   print('done', res) loop = asyncio.get_event_loop() loop.run_until_complete(bar()) 

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 konteks

Objek yang diharapkan dapat digunakan dalam manajer konteks sinkron atau asinkron, menyediakan metode khusus yang diperlukan. Sintaks:

 with await awaitable as a: #  'as'   #    async with 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 di

bahasa 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 #   MicroPython? try: import uasyncio as asyncio up = True #    sys.implementation.name except ImportError: import asyncio async def times_two(n): # Coro   await asyncio.sleep(1) return 2 * n class Foo(): def __await__(self): res = 1 for n in range(5): print('__await__ called') if up: # MicroPython res = yield from times_two(res) else: # CPython res = yield from times_two(res).__await__() return res __iter__ = __await__ async def bar(): foo = Foo() # foo is awaitable print('waiting for foo') res = await foo #   print('done', res) loop = asyncio.get_event_loop() loop.run_until_complete(bar()) 

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) #     if self.index >= len(self.data): return None x = self.data[self.index] self.index += 1 return x async def run(): ai = AsyncIterable() async for x in ai: print(x) 

4.3 Manajer konteks asinkron

Kelas 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() # a coro    async def return self async def __aexit__(self, *args): self.release() #   await asyncio.sleep_ms(0) 

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 Pengecualian

Jika 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') # ! raise #     . except KeyboardInterrupt: print('foo was interrupted by ctrl-c') #   ! raise async def shutdown(): print('Shutdown is running.') #     await asyncio.sleep(1) print('done') loop = asyncio.get_event_loop() try: loop.run_until_complete(bar()) except ZeroDivisionError: loop.run_until_complete(shutdown()) except KeyboardInterrupt: print('Keyboard interrupt at loop level.') loop.run_until_complete(shutdown()) 

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 habis

Seperti 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 pekerjaan

uasyncio 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: #  -  10 secs await asyncio.sleep(10) async def bar(loop): foo_instance = foo() #   coro loop.create_task(foo_instance) # code omitted asyncio.cancel(foo_instance) 

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: #  -  10 secs await asyncio.sleep(10) async def bar(loop): foo_instance = foo() #   coro loop.create_task(foo_instance) #    asyncio.cancel(foo_instance) await asyncio.sleep(0) #    

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: #  -  10 secs await asyncio.sleep(10) async def foo_runner(foo_instance): await foo_instance print('   ') async def bar(loop): foo_instance = foo() loop.create_task(foo_runner(foo_instance)) #    asyncio.cancel(foo_instance) 

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.

CatatanDilarang 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 timeout

Timeout 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') # And return async def foo(): await asyncio.wait_for(forever(), 5) await asyncio.sleep(2) loop = asyncio.get_event_loop() loop.run_until_complete(foo()) 

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 peralatan

Dasar 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 #   ISR while True: if my_flag: my_flag = False # service the device await asyncio.sleep(0) 

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 Sinkronisasi

Baik 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 coroutine

Ini 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): # Not __await__ issue #2678 data = b'' while not data.endswith(self.DELIMITER): yield from asyncio.sleep(0) # ,  : while not self.uart.any(): yield from asyncio.sleep(0) # timing may mean this is never called data = b''.join((data, self.uart.read(self.uart.any()))) self.data = data async def send_record(self, data): data = b''.join((data, self.DELIMITER)) self.uart.write(data) await self._send_complete() #          #        await asyncio.sleep(0) async def _send_complete(self): await asyncio.sleep(0.1) def read_record(self): # Synchronous: await the object before calling return self.data[0:-1] # Discard delimiter async def run(): foo = RecordOrientedUart() rx_data = b'' await foo.send_record(b'A line of text.') for _ in range(20): await foo #  coros       foo rx_data = foo.read_record() print('Got: {}'.format(rx_data)) await foo.send_record(rx_data) rx_data = b'' loop = asyncio.get_event_loop() loop.run_until_complete(run()) 

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 pengemudi

Program 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 ) Unit

input 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): #    def ioctl(self, req, arg): # see ports/stm32/uart.c ret = MP_STREAM_ERROR if req == MP_STREAM_POLL: ret = 0 if arg & MP_STREAM_POLL_RD: if hardware_has_at_least_one_char_to_read: ret |= MP_STREAM_POLL_RD if arg & MP_STREAM_POLL_WR: if hardware_can_accept_at_least_one_write_character: ret |= MP_STREAM_POLL_WR return ret 

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 ) #  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.py

Pengemudi 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 HTU21D

Sopir 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 Trik

7.1 Program dibekukan Pembekuan

biasanya 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 state

Saat 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 sampah

Anda 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 Pengujian

Dianjurkan 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) # ,  : while not self.uart.any(): yield from asyncio.sleep(0) #        data = b''.join((data, self.uart.read(self.uart.any()))) self.data = data 

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 error

Jika 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(): # code loop.create_task(foo) #  1 1: foo     foo() #  2: . 

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()) #   bar(foo) #     ,      bar(foo()) z = (foo,) z = (foo(),) 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 WiFi

Mekanisme 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. Dokumen

ini menjelaskan masalah yang saya temui dalam aplikasi WiFi yang membuat soket tetap terbuka untuk waktu yang lama dan menguraikan solusinya. Pltcm

menawarkan 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 peristiwa

Sebuah 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() #   get_event_loop() #     loop = asyncio.get_event_loop(runq_len=40, waitq_len=40) 

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() # get_event_loop()    

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 #      loop = asyncio.get_event_loop(runq_len=40, waitq_len=40) bar = my_module.Bar(loop) 

Masalah ini dibahas di sini .

8 catatan untuk pemula

Catatan 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 acara

Aplikasi 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() #    while True: time_now = utime.time() if time_now >= led_1_time: #  LED #1 led1.toggle() led_1_time = time_now + led_1_period if time_now >= led_2_time: #  LED #2 led2.toggle() led_2_time = time_now + led_2_period #    LEDs if switch.value() != switch_state: switch_state = switch.value() #  - if uart.any(): #    UART 

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() # -     period, #          

Penjadwal di uasyncio memungkinkan Anda membuat kelas-kelas seperti itu.

8.2 Masalah 2: Metode Pemblokiran

Misalkan 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 Uasyncio

Kelas 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 # ,   async def killer(): # ,      sw = pyb.Switch() while not sw.value(): await asyncio.sleep_ms(100) leds = [LED_async(n) for n in range(1, 4)] for n, led in enumerate(leds): led.flash(0.7 + n/4) loop = asyncio.get_event_loop() loop.run_until_complete(killer()) 

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()) #    #       killer (), #   . 

8.4 Perencanaan dalam uasyncio

Python 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 #  flag = False #     

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) #  flag = False #     

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 Interaksi

Dalam 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 .

Source: https://habr.com/ru/post/id484472/


All Articles