Apa itu pemrograman asinkron. Pemrograman asinkron

Untuk beberapa waktu sekarang saya mendapat banyak pertanyaan tentang pemrograman asynchronous. Dan, menyadari bahwa topik ini menarik bagi banyak pembaca saya, saya memutuskan untuk menulis artikel untuk menjelaskan istilah-istilah ini, terutama karena Pemrograman asinkron adalah bagian yang sangat penting dari Internet modern.

Untuk memulainya, perlu dicatat bahwa ada dua konsep yang sangat berbeda: yang pertama adalah model pemrograman sinkron dan asinkron, dan yang kedua - lingkungan berulir tunggal dan multi-utas. Masing-masing model pemrograman (sinkron dan asinkron) dapat bekerja di lingkungan single-threaded dan multi-threaded.

Model pemrograman sinkron.

Dalam model pemrograman ini, sebuah thread ditugaskan ke satu tugas dan mulai mengerjakannya. Setelah tugas selesai, thread tersedia untuk tugas berikutnya. Itu. satu tugas akan digantikan oleh tugas lain secara berurutan. Dalam model ini, tidak mungkin meninggalkan tugas di tengah untuk menyelesaikan tugas lainnya. Mari kita bahas cara kerja model ini di lingkungan single-thread dan multi-thread.

Lingkungan Berulir Tunggal- jika kita memiliki beberapa tugas yang perlu dijalankan dan sistem saat ini hanya menyediakan satu thread, maka tugas tersebut ditugaskan ke thread satu per satu. Secara visual dapat digambarkan seperti ini:

Di mana Benang 1 - satu benang, Tugas 1 dan Tugas 2, Tugas 3, Tugas 4 – tugas yang relevan.

Kami melihat bahwa kami memiliki aliran ( benang 1) Dan empat tugas yang perlu diselesaikan. Sebuah thread mulai mengerjakan tugas dan menyelesaikan semua tugas satu per satu.

Lingkungan multithread - Multi-Thread- dalam lingkungan ini kami menggunakan beberapa thread yang dapat melakukan tugas-tugas ini secara bersamaan. Artinya kita punya kumpulan benang(utas baru juga dapat dibuat sesuai kebutuhan berdasarkan sumber daya yang tersedia) dan banyak tugas.

Kami melihat bahwa kami memiliki empat thread dan jumlah tugas yang sama. Oleh karena itu, setiap thread melakukan satu tugas dan menyelesaikannya. Ini adalah skenario yang ideal, namun dalam keadaan normal kita cenderung memiliki lebih banyak tugas daripada jumlah thread yang tersedia. Jadi, ketika satu thread selesai menjalankan suatu tugas, thread tersebut akan segera mulai menjalankan tugas lainnya. Perhatikan juga bahwa thread baru tidak dibuat setiap saat karena memerlukan sumber daya sistem seperti siklus CPU dan memori, yang mungkin tidak cukup.

Sekarang mari kita bicara tentang model asynchronous dan bagaimana perilakunya dalam lingkungan single-thread dan multi-thread.

Model pemrograman asinkron.

Berbeda dengan model pemrograman sinkron, di sini satu thread, yang memulai tugas tertentu, dapat menghentikan eksekusinya untuk jangka waktu tertentu, sambil mempertahankan statusnya saat ini, dan mulai menjalankan tugas lain.

kita melihat bahwa satu utas bertanggung jawab untuk melaksanakan semua tugas, bergantian satu sama lain.

Jika sistem kami mampu membuat banyak thread, maka semua thread dapat bekerja dalam model asinkron.

Kami melihat tugas yang sama T4, T5, T6 diproses oleh banyak thread. Inilah keindahan dan kekuatan naskah ini. Seperti yang Anda lihat, tugasnya T4 diluncurkan pertama kali di thread benang 1 dan selesai di thread benang 2. Serupa T6 berakhir pada Benang 2, Benang 3 dan Benang 4.

Jadi, kami memiliki total empat skenario -

  • Utas tunggal sinkron
  • Multi-utas sinkron
  • Utas tunggal asinkron
  • Multi-utas asinkron

Manfaat Pemrograman Asinkron

Untuk aplikasi apa pun, ada dua hal yang penting: kegunaan dan kinerja. Kegunaan penting karena ketika pengguna mengklik tombol untuk menyimpan beberapa data, diperlukan beberapa tugas kecil untuk dilakukan, seperti membaca dan mengisi data di objek internal, membuat koneksi ke server SQL dan menyimpan kueri di sana, dll. . dll.

Karena server SQL, misalnya, kemungkinan besar berjalan di komputer lain di jaringan dan berjalan di bawah proses yang berbeda, hal ini dapat memakan waktu lama. Dan, jika aplikasi berjalan pada satu thread, maka layar perangkat pengguna akan tetap dalam keadaan tidak aktif hingga semua tugas selesai, yang merupakan contoh antarmuka pengguna yang sangat buruk. Inilah sebabnya mengapa banyak aplikasi dan kerangka kerja baru bergantung sepenuhnya pada model asinkron, karena memungkinkan Anda melakukan banyak tugas sambil mempertahankan antarmuka yang responsif.

Efisiensi aplikasi juga sangat penting. Diperkirakan saat menjalankan kueri, sekitar 70-80% waktu terbuang untuk menunggu tugas yang bergantung. Oleh karena itu, di sinilah pemrograman asinkron berguna.

Jadi, dalam artikel ini kita melihat apa itu pemrograman sinkron dan asinkron. Penekanan khusus diberikan pada pemrograman asinkron, karena ini mendasari sebagian besar alat pengembangan modern. Dan pada artikel berikut ini kita akan berkenalan dengan contoh nyata penggunaan model asynchronous.

Anotasi: Pemrograman asinkron di .NET Framework. Metode EndOperation, Pooling, Callback. Peluncuran metode arbitrer yang tidak sinkron. Pembaruan antarmuka. Keamanan aplikasi multi-thread. Sinkronisasi: otomatis, manual; penggunaan area sinkronisasi. Kontrol ProgressBar

Ketika kita menjalankan metode "panjang", prosesor akan mengeksekusinya, sementara itu "tidak punya waktu" untuk memenuhi permintaan pengguna lainnya (Gambar 7.2).

Saat berjalan secara sinkron, aplikasi hanya memiliki satu thread. Dengan menggunakan model asinkron Dalam pemrograman, Anda dapat menjalankan beberapa thread paralel dan bereaksi terhadap tindakan pengguna baru saat thread tersebut berjalan. Setelah n-thread dijalankan, Anda menampilkan hasilnya di layar.

Dalam kehidupan sehari-hari, terdapat banyak contoh perilaku asinkron - pengisian bahan baku di gudang memungkinkan pabrik memastikan pengoperasian tidak terganggu. Model yang tidak sinkron adalah penggunaan bahan mentah secara menyeluruh dan waktu henti berikutnya saat menunggu pengiriman bahan.

Dukungan untuk pemrograman asinkron di .NET Framework

Kelas yang memiliki dukungan bawaan model asinkron, memiliki sepasang metode asinkron untuk masing-masing metode sinkron. Metode ini diawali dengan kata Begin dan End. Misalnya, jika kita ingin menggunakan metode Baca versi asinkron dari kelas System.IO.Stream, kita perlu menggunakan metode BeginRead dan EndRead dari kelas yang sama.

Untuk menggunakan dukungan bawaan model asinkron pemrograman, Anda perlu memanggil metode BeginOperation yang sesuai dan memilih model penyelesaian panggilan. Memanggil metode BeginOperation akan mengembalikan objek antarmuka IAsyncResult yang menentukan status eksekusi operasi asinkron. Ada beberapa cara untuk menghentikan metode asinkron.

Metode Operasi Akhir

Metode EndOperation digunakan untuk mengakhiri panggilan asinkron ketika thread utama perlu melakukan komputasi dalam jumlah besar yang tidak bergantung pada hasil pemanggilan metode asinkron. Setelah pekerjaan utama selesai dan aplikasi memerlukan hasil metode asinkron untuk tindakan lebih lanjut, metode EndOperation dipanggil. Dalam hal ini, thread utama akan ditangguhkan hingga metode asinkron selesai. Contoh penggunaan metode ini:

Menggunakan Sistem; menggunakan Sistem.IO; namespace EndOperation ( class Class1 ( static void Main(string args) ( //Buat aliran dan buka file. FileStream fs = new FileStream("text.txt", FileMode.Open); byte fileBytes = byte baru; // Jalankan metode Baca di thread paralel.IAsyncResult ar = fs.BeginRead(fileBytes, 0, fileBytes.Length, null, null); for(int i = 0; i<10000000; i++) { // Имитация длительной работы основного // потока, не зависящая от выполнения асинхронного метода. } // После завершения работы основного потока // запускаем завершение выполнения параллельного // метода Read. fs.EndRead(ar); Console.Write(System.Text.Encoding.Default.GetString(fileBytes)); } } } Листинг 7.1.

Untuk mengakhiri eksekusi thread paralel ar, metode EndRead dipanggil di sini. Sebagai kode yang mensimulasikan pekerjaan jangka panjang, Anda dapat menghubungkan penghitung penyelesaian tugas yang tepat, yang telah kita bahas di “Menggunakan pustaka kode dalam formulir Windows”.

Metode pengumpulan

Metode ini cocok jika Anda perlu mengontrol eksekusi metode asinkron. Saat menggunakannya, Anda harus memeriksa eksekusi metode asynchronous secara manual, menggunakan properti IsCompleted dari objek bertipe IAsyncResult. Ini bukan teknik penghentian yang paling umum karena sebagian besar proses tidak memerlukan kontrol eksekusi. Contoh penggunaan metode polling:

Menggunakan Sistem; menggunakan Sistem.IO; namespace Pooling ( class Class1 ( static void Main(string args) ( FileStream fs = new FileStream("text.txt", FileMode.Open); byte fileBytes = new byte; Console.Write("Jalankan metode Baca secara asinkron.") ; // Jalankan metode Baca secara asinkron. IAsyncResult ar = fs.BeginRead(fileBytes, 0, fileBytes.Length, null, null); // Periksa eksekusi metode asinkron. while(!ar.IsCompleted) ( // While membaca file di layar menampilkan pesan Console.Write("Proses sedang berlangsung"); ) Console.WriteLine(); string textFromFile = System.Text.Encoding.Default.GetString(fileBytes); Console.Write(textFromFile); ) ) ) Daftar 7.2.

Kemungkinan besar, Anda tidak akan melihat munculnya tulisan selama proses - file akan dibaca terlalu cepat. Untuk menguji metode ini, tulis file teks besar ke floppy disk, tentukan alamatnya, dan coba jalankan kembali aplikasi.

Metode panggilan balik

Metode Callback untuk mengakhiri panggilan asinkron digunakan jika Anda ingin mencegah pemblokiran thread utama. Saat menggunakan Callback, kami menjalankan metode EndOperation di badan metode, yang dipanggil ketika metode yang berjalan di thread paralel selesai. Tanda tangan metode yang dipanggil harus cocok dengan tanda tangan delegasi AsyncCallback.

Contoh penggunaan opsi Callback:

Menggunakan Sistem; menggunakan Sistem.IO; namespace Callback ( class Class1 ( // Membuat aliran dan array byte. static FileStream fs; static byte fileBytes; static void Main(string args) ( // Membuka file ke dalam aliran. fs = new FileStream("text. txt", FileMode. Open); fileBytes = new byte; // Jalankan metode Read secara asinkron, meneruskan metode WorkComplete fs.BeginRead(fileBytes, 0, (int)fs.Length, new AsyncCallback(WorkComplete), null); ) // /

/// Metode dipanggil secara otomatis ketika thread paralel berakhir. /// /// Objek bertipe IAsyncResult. static void WorkComplete(IAsyncResult ar) ( // Memulai akhir metode. fs.EndRead(ar); string textFromFile = System.Text.Encoding.Default.GetString(fileBytes); Console.Write(textFromFile); ) ) ) Daftar 7.3.

Pada disk yang disertakan dengan buku ini Anda akan menemukan aplikasi EndOperation, Pooling dan Callback (Code\Glava7\EndOperation, Pooling, Callback).

Asinkroni dalam pemrograman

Ivan Borisov

Pemrograman tradisional menggunakan pemrograman sinkron - eksekusi instruksi berurutan dengan panggilan sistem sinkron yang sepenuhnya memblokir thread eksekusi hingga operasi sistem, seperti membaca dari disk, selesai. Sebagai contoh, server gema ditulis di bawah ini:

While (true) ( ​​std::string data; auto socket = Socket(localhost, port); socket.wait_connection(); while (!socket.end_of_connection()) ( data = socket.read(); // Pemblokiran socket.write(data); // Kunci ) )

Ketika metode read() dan write() dipanggil, thread eksekusi saat ini akan terputus saat menunggu I/O jaringan. Terlebih lagi, seringkali program hanya menunggu. Dalam sistem dengan muatan tinggi, inilah yang paling sering terjadi - hampir sepanjang waktu program menunggu sesuatu: disk, DBMS, jaringan, UI, secara umum, beberapa peristiwa eksternal yang tidak bergantung pada program itu sendiri. Pada sistem dengan beban ringan, hal ini dapat diselesaikan dengan membuat thread baru untuk setiap tindakan pemblokiran. Saat satu utas sedang tidur, utas lainnya berfungsi.

Namun apa yang harus dilakukan jika penggunanya banyak? Jika Anda membuat setidaknya satu thread untuk masing-masing thread, kinerja server tersebut akan turun tajam karena konteks eksekusi thread terus berubah. Selain itu, setiap thread memiliki konteks eksekusinya sendiri, termasuk memori tumpukan, yang memiliki ukuran minimal 4 KB. Pemrograman asinkron dapat mengatasi masalah ini.

asinkron

Asinkroni dalam pemrograman adalah eksekusi suatu proses dalam mode panggilan sistem non-pemblokiran, yang memungkinkan thread program untuk melanjutkan pemrosesan. Ada beberapa cara untuk mengimplementasikan pemrograman asinkron, yang akan Anda pelajari di bawah.

Panggilan balik

Untuk menulis program asinkron, Anda dapat menggunakan fungsi panggilan balik (dari bahasa Inggris callback - callback) - fungsi yang akan dipanggil secara asinkron oleh beberapa pengendali peristiwa setelah tugas selesai. Contoh server yang ditulis ulang menggunakan fungsi panggilan balik:

While (true) ( ​​​​auto socket = Socket(localhost, port); socket.wait_connection(); // Masih ada pemblokiran socket.async_read((auto &data) /* * Thread tidak diblokir, fungsi lambda akan dipanggil * setiap kali setelah menerima data baru dari soket, * dan thread utama akan membuat soket baru dan * menunggu koneksi baru. */ ( socket.async_write(data, (auto &socket) ( if ( soket.end_of_connection()) soket.close(); ) ); )); )

Di wait_connection() kita masih menunggu sesuatu, tetapi sekarang, di dalam fungsi wait_connection(), sesuatu seperti penjadwal OS dapat diimplementasikan, tetapi dengan fungsi panggilan balik (sambil menunggu koneksi baru, mengapa tidak memproses yang lama? ?Misalnya melalui antrian). Fungsi panggilan balik dipanggil jika data baru muncul di soket - lambda di async_read(), atau data telah ditulis - lambda di async_write().

Hasilnya, kami mendapatkan operasi asinkron dari beberapa koneksi dalam satu thread, yang akan lebih jarang menunggu. Asinkroni ini juga dapat diparalelkan untuk mendapatkan keuntungan penuh dari pemanfaatan waktu CPU.

Ada beberapa masalah dengan pendekatan ini. Yang pertama secara bercanda disebut callback hell. Cukup dengan mencari gambar di Google tentang topik ini untuk memahami betapa tidak dapat dibaca dan jeleknya topik ini. Dalam contoh kita hanya ada dua fungsi callback yang disarangkan, namun masih banyak lagi.

Masalah kedua adalah kode tidak lagi terlihat sinkron: ada "lompatan" dari wait_connection() ke lambda, seperti lambda yang diteruskan ke async_write() , yang memutus urutan kode, sehingga tidak mungkin untuk memprediksi urutan apa lambda dipanggil. Hal ini membuat kode sulit dibaca dan dipahami.

Asinkron/Tunggu

Mari kita coba membuat kode asynchronous agar terlihat sinkron. Untuk pemahaman yang lebih baik, mari kita ubah tugasnya sedikit: sekarang kita perlu membaca data dari DBMS dan file menggunakan kunci yang dikirimkan melalui jaringan, dan mengirimkan hasilnya kembali melalui jaringan.

Pekerjaan async void publik() ( var db_conn = Db_connection(localhost); var socket = Socket(localhost, port); socket.wait_connection(); var data = socket.async_read(); var db_data = db_conn.async_get(menunggu data) ; var file_data = File(menunggu data).async_read(); menunggu socket.async_write($”(menunggu db_data) (menunggu file_data)”); socket.close(); )

Mari kita lihat programnya baris demi baris:

  • Kata kunci async di header fungsi memberi tahu kompiler bahwa fungsi tersebut asinkron dan harus dikompilasi secara berbeda. Bagaimana tepatnya dia akan melakukan ini tertulis di bawah.
  • Tiga baris pertama fungsinya: membuat dan menunggu koneksi.
  • Baris berikutnya melakukan pembacaan asinkron tanpa mengganggu thread utama eksekusi.
  • Dua baris berikutnya membuat permintaan asinkron ke database dan membaca file. Operator menunggu menjeda fungsi saat ini hingga tugas pembacaan asinkron dari database dan file selesai.
  • Baris terakhir melakukan penulisan asinkron ke soket, tetapi hanya setelah kita menunggu pembacaan asinkron dari database dan file.

Ini lebih cepat daripada menunggu database secara berurutan, lalu file. Dalam banyak implementasi, kinerja async/menunggu lebih baik daripada fungsi panggilan balik klasik, sementara kode tersebut dibaca sebagai sinkron.

Coroutine

Mekanisme yang dijelaskan di atas disebut coroutine. Anda sering mendengar varian “coroutine” (dari bahasa Inggris coroutine - coroutine).

Beberapa titik masuk

Pada dasarnya, coroutine adalah fungsi yang memiliki banyak titik masuk dan keluar. Fungsi normal hanya memiliki satu titik masuk dan beberapa titik keluar. Jika kita kembali ke contoh di atas, titik masuk pertama adalah pemanggilan fungsi itu sendiri menggunakan operator async, kemudian fungsi tersebut akan menghentikan eksekusinya alih-alih menunggu database atau file. Semua penantian selanjutnya tidak akan memulai ulang fungsi, namun akan melanjutkan eksekusinya pada titik gangguan sebelumnya. Ya, dalam banyak bahasa mungkin ada beberapa menunggu di coroutine.

Untuk pemahaman yang lebih baik, mari kita lihat kode dengan Python:

Def async_factorial(): hasil = 1 sedangkan True: menghasilkan hasil hasil *= i fac = async_factorial() untuk i dalam rentang(42): print(berikutnya(fac))

Program akan menampilkan seluruh barisan bilangan faktorial dengan bilangan dari 0 sampai 41.

Fungsi async_factorial() akan mengembalikan objek generator yang dapat diteruskan ke fungsi next(), yang akan terus mengeksekusi coroutine hingga pernyataan hasil berikutnya, menjaga status semua variabel fungsi lokal. Fungsi next() mengembalikan pernyataan hasil di dalam coroutine. Jadi fungsi async_factorial(), secara teori, memiliki beberapa titik masuk dan keluar.

Bertumpuk dan Tanpa Tumpukan

Bergantung pada penggunaan tumpukan, coroutine dibagi menjadi tumpukan, di mana setiap coroutine memiliki tumpukannya sendiri, dan tanpa tumpukan, di mana semua variabel fungsi lokal disimpan dalam objek khusus.

Karena di coroutine kita dapat meletakkan pernyataan hasil di mana saja, kita perlu menyimpan seluruh konteks fungsi di suatu tempat, yang mencakup bingkai pada tumpukan (variabel lokal) dan informasi meta lainnya. Hal ini dapat dilakukan, misalnya, dengan mengganti tumpukan sepenuhnya, seperti yang dilakukan pada coroutine tumpukan.

Pada gambar di bawah, panggilan async membuat bingkai tumpukan baru dan mengalihkan eksekusi thread ke bingkai tersebut. Ini praktis merupakan thread baru, hanya saja thread ini akan dieksekusi secara asinkron dengan thread utama.

pada gilirannya, hasil mengembalikan bingkai tumpukan sebelumnya untuk dieksekusi, menyimpan referensi ke akhir bingkai saat ini di tumpukan sebelumnya.

Memiliki tumpukan sendiri memungkinkan Anda menghasilkan dari panggilan fungsi yang disarangkan, namun panggilan tersebut disertai dengan pembuatan/perubahan lengkap konteks eksekusi program, yang lebih lambat dibandingkan coroutine tanpa tumpukan.

Lebih produktif, namun pada saat yang sama lebih terbatas, adalah coroutine tanpa tumpukan. Mereka tidak menggunakan tumpukan, dan kompiler mengubah fungsi yang berisi coroutine menjadi mesin status tanpa coroutine. Misalnya kode:

Def fib(): a = 0 b = 1 sedangkan Benar: hasil a a += b hasil b b += a

Akan dikonversi ke pseudocode berikut:

Fib kelas: def __init__(self): self.a = 0 self.b = 1 self.__result: int self.__state = 0 def __next__(self): while True: if self.__state == 0: self.a = 0 self.b = 1 jika self.__state == 0 atau self.__state == 3: self.__result = self.a self.__state = 1 kembalikan self.__result jika self.__state == 1: self.a += self.b self.__result = self.b self.__state = 2 kembalikan self.__result jika self.__state == 2: self.b += a self.__state = 3 break

Pada dasarnya, ini menciptakan sebuah kelas yang menyimpan semua status fungsi, serta titik terakhir di mana hasil dipanggil. Ada masalah dengan pendekatan ini: hasil hanya dapat dipanggil di dalam isi fungsi coroutine, namun tidak dari dalam fungsi yang disarangkan.

Simetris dan asimetris

Coroutine juga dibagi menjadi simetris dan asimetris.

Simetris memiliki penjadwal coroutine global yang memilih, di antara semua operasi asinkron yang tertunda, operasi yang harus dijalankan berikutnya. Contohnya adalah penjadwal yang dibahas di awal fungsi wait_connection().

DI DALAM asimetris coroutine tidak memiliki penjadwal global, dan pemrogram, bersama dengan dukungan kompiler, memilih coroutine mana yang akan dieksekusi dan kapan. Sebagian besar implementasi coroutine bersifat asimetris.

Kesimpulan

Pemrograman asinkron adalah alat yang sangat ampuh untuk mengoptimalkan program dengan beban tinggi dengan sistem menunggu yang sering. Namun seperti teknologi kompleks lainnya, teknologi tidak dapat digunakan hanya karena sudah ada. Anda harus selalu bertanya pada diri sendiri pertanyaan: apakah saya memerlukan teknologi ini? Manfaat praktis apa yang akan diberikannya kepada saya? Jika tidak, pengembang berisiko menghabiskan banyak tenaga, waktu, dan uang tanpa menghasilkan keuntungan apa pun.

Pembaruan terakhir: 17/10/2018

Asynchrony memungkinkan Anda memindahkan tugas individual dari thread utama ke metode asinkron khusus atau blok kode. Hal ini terutama berlaku dalam program grafis, di mana tugas yang panjang dapat memblokir antarmuka pengguna. Dan untuk mencegah hal ini terjadi, Anda perlu menggunakan asynchrony. Asynchrony juga memiliki keunggulan dalam aplikasi web saat memproses permintaan dari pengguna, saat mengakses database atau sumber daya jaringan. Untuk kueri database yang besar, metode asinkron akan tertidur sebentar hingga menerima data dari database, dan thread utama dapat melanjutkan pekerjaannya. Dalam aplikasi sinkron, jika kode untuk menerima data ada di thread utama, thread ini hanya akan memblokir saat menerima data.

Kunci untuk bekerja dengan panggilan asinkron di C# adalah dua kata kunci: async dan menunggu , yang tujuannya adalah untuk mempermudah penulisan kode asinkron. Mereka digunakan bersama untuk membuat metode asinkron.

Metode asinkron mempunyai ciri-ciri sebagai berikut:

    Header metode menggunakan pengubah async

    Metode ini berisi satu atau lebih ekspresi menunggu

    Tipe kembaliannya adalah salah satu dari berikut ini:

    • NilaiTugas

Metode asinkron, seperti metode biasa, dapat menggunakan sejumlah parameter atau tidak menggunakannya sama sekali. Namun, metode asinkron tidak dapat menentukan parameter dengan pengubah out dan ref.

Perlu juga dicatat bahwa kata async, yang ditentukan dalam definisi suatu metode, tidak secara otomatis membuat metode tersebut menjadi asynchronous. Ini hanya menunjukkan bahwa metode tersebut mungkin berisi satu atau lebih ekspresi menunggu.

Mari kita lihat contoh metode asynchronous:

Menggunakan Sistem; menggunakan Sistem.Threading; menggunakan System.Threading.Tasks; namespace HelloApp ( Program kelas ( static void Factorial() ( int hasil = 1; for(int i = 1; i<= 6; i++) { result *= i; } Thread.Sleep(8000); Console.WriteLine($"Факториал равен {result}"); } // определение асинхронного метода static async void FactorialAsync() { Console.WriteLine("Начало метода FactorialAsync"); // выполняется синхронно await Task.Run(()=>Faktorial()); // dieksekusi secara asinkron Console.WriteLine("Akhir dari metode FactorialAsync"); ) static void Main(string args) ( FactorialAsync(); // memanggil metode asinkron Console.WriteLine("Masukkan nomor: "); int n = Int32.Parse(Console.ReadLine()); Console.WriteLine($ "Bilangan kuadrat sama dengan (n * n)"); Console.Read(); ) ) )

Di sini, pertama-tama, metode penghitungan faktorial yang biasa didefinisikan. Untuk mensimulasikan waktu berjalan yang lama, digunakan penundaan 8 detik menggunakan metode Thread.Sleep(). Secara konvensional, ini adalah metode yang melakukan beberapa pekerjaan dalam jangka waktu yang lama. Namun untuk lebih mudah memahaminya, cukup menghitung faktorial dari 6.

Ini juga mendefinisikan metode asinkron FactorialAsync() . Ini asinkron karena memiliki pengubah async di depan tipe kembalian dalam definisi, tipe kembaliannya tidak berlaku, dan ekspresi menunggu didefinisikan dalam badan metode.

Ekspresi menunggu menentukan tugas yang akan dieksekusi secara asinkron. Dalam hal ini, tugas serupa mewakili implementasi fungsi faktorial:

Tunggu Task.Run(()=>Factorial());

Menurut aturan tak tertulis, biasanya menggunakan akhiran Async - FactorialAsync () atas nama metode asinkron, meskipun pada prinsipnya hal ini tidak diperlukan.

Kami mendapatkan faktorial itu sendiri dalam metode asinkron FactorialAsync. Ini asynchronous karena dideklarasikan dengan pengubah async dan berisi penggunaan kata kunci menunggu.

Dan dalam metode Utama kami menyebutnya metode asinkron.

Mari kita lihat seperti apa keluaran konsol program ini:

Awal dari metode FactorialAsync Masukkan angka : 7 Kuadrat dari angka tersebut adalah 49 Akhir dari metode Utama Faktorial adalah 720 Akhir dari metode FactorialAsync

Mari kita lihat apa yang terjadi di sini langkah demi langkah:

    Metode Utama dijalankan, yang memanggil metode FactorialAsync asinkron.

    Metode FactorialAsync mulai dijalankan secara sinkron hingga ekspresi menunggu.

    Ekspresi menunggu menjalankan tugas asinkron Task.Run(()=>Factorial())

    Saat tugas asinkron Task.Run(()=>Factorial()) sedang berjalan (dan dapat berjalan cukup lama), eksekusi kode kembali ke metode pemanggilan - yaitu, ke metode Utama. Pada metode Utama, kita akan diminta memasukkan angka untuk menghitung kuadrat angka tersebut.

    Ini adalah keuntungan dari metode asinkron - tugas asinkron yang dapat berjalan cukup lama tidak memblokir metode Utama, dan kita dapat terus mengerjakannya, misalnya memasukkan dan memproses data.

    Ketika tugas asinkron telah menyelesaikan eksekusinya (dalam kasus di atas, tugas tersebut telah menghitung faktorial suatu bilangan), metode FactorialAsync asinkron, yang disebut tugas asinkron, terus bekerja.

Fungsi faktorial mungkin bukan contoh terbaik, karena pada kenyataannya tidak ada gunanya menjadikannya asinkron dalam kasus ini. Tapi mari kita lihat contoh lain - membaca dan menulis file:

Menggunakan Sistem; menggunakan Sistem.Threading; menggunakan System.Threading.Tasks; menggunakan Sistem.IO; namespace HelloApp ( Program kelas ( static async void ReadWriteAsync() ( string s = "Halo dunia! Selangkah demi selangkah"; // hello.txt - file yang akan ditulis dan dibaca menggunakan (StreamWriter writer = new StreamWriter(" halo .txt", salah)) ( menunggu penulis.WriteLineAsync(s); // penulisan asinkron ke file ) menggunakan (StreamReader reader = new StreamReader("hello.txt")) ( hasil string = menunggu pembaca.ReadToEndAsync() ; // pembacaan asinkron dari file Console.WriteLine(result); ) ) static void Main(string args) ( ReadWriteAsync(); Console.WriteLine("Beberapa pekerjaan"); Console.Read(); ) ) )

Metode ReadWriteAsync() asinkron menulis string ke file dan kemudian membaca file tertulis. Operasi semacam itu bisa memakan waktu lama, terutama dengan data dalam jumlah besar, jadi lebih baik melakukan operasi tersebut secara asinkron.

Kerangka .NET sudah memiliki dukungan bawaan untuk operasi tersebut. Misalnya, kelas StreamWriter mendefinisikan metode WriteLineAsync(). Faktanya, ini sudah mewakili operasi asinkron dan mengambil string tertentu sebagai parameter yang harus ditulis ke file. Karena metode ini mewakili operasi asinkron, kita dapat membingkai panggilan ke metode ini sebagai ekspresi menunggu:

Tunggu writer.WriteLineAsync(s); // penulisan asinkron ke file

Demikian pula, kelas StreamReader mendefinisikan metode ReadToEndAsync(), yang juga mewakili operasi asinkron dan mengembalikan semua teks yang dibaca.

Kerangka kerja .NET Core mendefinisikan banyak metode serupa. Biasanya, mereka terkait dengan bekerja dengan file, mengirim permintaan jaringan, atau permintaan basis data. Mereka mudah dikenali dengan akhiran Async. Artinya, jika suatu metode memiliki akhiran serupa pada namanya, maka kemungkinan besar metode tersebut akan digunakan dalam ekspresi menunggu.

Kekosongan statis Utama(string args) ( ReadWriteAsync(); Console.WriteLine("Beberapa berhasil"); Console.Read(); )

Sekali lagi, ketika eksekusi dalam metode ReadWriteAsync mencapai ekspresi menunggu pertama, kontrol kembali ke metode Utama dan kita dapat terus mengerjakannya. Menulis ke file dan membaca file akan dilakukan secara paralel dan tidak akan menghalangi pengoperasian metode Utama.

Mendefinisikan Operasi Asinkron

Seperti disebutkan di atas, kerangka .NET Core memiliki banyak metode bawaan yang mewakili operasi asinkron. Mereka diakhiri dengan akhiran Async. Dan sebelum memanggil metode tersebut, kita dapat menentukan operator menunggu. Misalnya:

Penulis StreamWriter = new StreamWriter("hello.txt", false); menunggu penulis.WriteLineAsync("Halo"); // penulisan asinkron ke file

Atau kita dapat mendefinisikan sendiri operasi asinkron menggunakan metode Task.Run() :

Static void Factorial() ( int hasil = 1; for (int i = 1; i<= 6; i++) { result *= i; } Thread.Sleep(8000); Console.WriteLine($"Факториал равен {result}"); } // определение асинхронного метода static async void FactorialAsync() { await Task.Run(()=>Faktorial()); // memanggil operasi asinkron)

Anda dapat menentukan operasi asinkron menggunakan ekspresi lambda:

Async statis void FactorialAsync() ( menunggu Task.Run(() => ( int hasil = 1; for (int i = 1; i<= 6; i++) { result *= i; } Thread.Sleep(8000); Console.WriteLine($"Факториал равен {result}"); }); }

Melewati parameter ke operasi asinkron

Di atas kita menghitung faktorial dari 6, tapi misalkan kita ingin menghitung faktorial dari bilangan yang berbeda:

Menggunakan Sistem; menggunakan Sistem.Threading; menggunakan System.Threading.Tasks; namespace HelloApp ( Program kelas ( static void Faktorial(int n) ( int hasil = 1; for (int i = 1; i<= n; i++) { result *= i; } Thread.Sleep(5000); Console.WriteLine($"Факториал равен {result}"); } // определение асинхронного метода static async void FactorialAsync(int n) { await Task.Run(()=>Faktorial(n)); ) static void Main(string args) ( FactorialAsync(5); FactorialAsync(6); Console.WriteLine("Beberapa pekerjaan"); Console.Read(); ) ) )

Mendapatkan hasil dari operasi asinkron

Operasi asinkron dapat mengembalikan beberapa hasil, yang dapat kita peroleh dengan cara yang sama seperti saat memanggil metode biasa:

Menggunakan Sistem; menggunakan Sistem.Threading; menggunakan System.Threading.Tasks; namespace HelloApp ( Program kelas ( static int Faktorial(int n) ( int hasil = 1; untuk (int i = 1; i<= n; i++) { result *= i; } return result; } // определение асинхронного метода static async void FactorialAsync(int n) { int x = await Task.Run(()=>Faktorial(n)); Console.WriteLine($"Faktorial adalah (x)"); ) static void Utama(string args) ( FactorialAsync(5); FactorialAsync(6); Console.Read(); ) ) )

Metode Faktorial mengembalikan nilai bertipe int, kita bisa mendapatkan nilai ini hanya dengan menugaskan hasil operasi asinkron ke variabel bertipe ini: int x = menunggu Task.Run(()=>Factorial(n));