Hari yang baik Kali ini kita akan berbicara tentang sebuah topik yang mulai dipahami oleh setiap penganut bahasa C # yang menghargai diri sendiri - pemrograman asinkron menggunakan Tugas atau, pada orang umum, async / menunggu. Microsoft melakukan pekerjaan yang baik - untuk menggunakan asinkron dalam kebanyakan kasus Anda hanya perlu mengetahui sintaks dan tidak ada detail lainnya. Tetapi jika Anda masuk lebih dalam, topiknya cukup banyak dan kompleks. Itu dinyatakan oleh banyak, masing-masing dengan gayanya sendiri. Ada banyak artikel keren tentang topik ini, tetapi masih ada banyak kesalahpahaman di sekitarnya. Kami akan mencoba memperbaiki situasi dan mengunyah materi sebanyak mungkin, tanpa mengorbankan kedalaman atau pemahaman.

Topik / bab yang dibahas:
- Konsep asinkron - manfaat asinkron dan mitos tentang utas "diblokir"
- TAP. Sintaks dan kondisi kompilasi - prasyarat untuk menulis metode kompilasi
- Bekerja dengan penggunaan TAP - mekanisme dan perilaku program dalam kode asinkron (membebaskan utas, memulai tugas dan menunggu mereka selesai)
- Di belakang layar: mesin negara - ikhtisar transformasi kompiler dan kelas yang dihasilkannya
- Asal-usul asynchrony. Perangkat metode asinkron standar - metode asinkron untuk bekerja dengan file dan jaringan dari dalam
- Kelas dan trik TAP adalah trik bermanfaat yang dapat membantu Anda mengelola dan mempercepat program menggunakan TAP
Konsep asinkron
Asynchrony itu sendiri jauh dari baru. Asynchrony biasanya menyiratkan melakukan operasi dalam gaya yang tidak berarti memblokir utas panggilan, yaitu, memulai operasi tanpa menunggu penyelesaiannya. Memblokir tidak seburuk yang dijelaskan. Satu dapat menemukan klaim yang diblokir thread menghabiskan waktu CPU, bekerja lebih lambat dan menyebabkan hujan. Apakah yang terakhir tampaknya tidak mungkin? Faktanya, 2 poin sebelumnya sama.
Pada tingkat penjadwal OS, ketika utas berada dalam kondisi "diblokir", waktu prosesor yang berharga tidak akan dialokasikan untuk itu. Penjadwal panggilan, sebagai aturan, jatuh pada operasi yang menyebabkan pemblokiran, penghenti waktu, dan gangguan lainnya. Yaitu, ketika, misalnya, pengontrol disk menyelesaikan operasi baca dan memulai interupsi yang sesuai, penjadwal dimulai. Dia akan memutuskan apakah akan memulai utas yang diblokir oleh operasi ini, atau yang lain dengan prioritas lebih tinggi.
Pekerjaan yang lambat tampaknya bahkan lebih absurd. Sesungguhnya, pekerjaan itu satu dan sama. Hanya operasi asinkron yang akan menambah sedikit overhead.
Tantangan hujan umumnya bukan sesuatu dari daerah ini.
Masalah pemblokiran utama adalah konsumsi sumber daya komputer yang tidak masuk akal. Bahkan jika kita lupa waktu untuk membuat utas dan bekerja dengan kumpulan utas, maka setiap utas yang diblokir akan menghabiskan ruang ekstra. Nah, ada skenario di mana hanya satu utas dapat melakukan pekerjaan tertentu (misalnya, utas UI). Karenanya, saya tidak ingin dia sibuk dengan tugas yang dapat dilakukan utas lain, mengorbankan kinerja operasi yang eksklusif untuknya.
Asynchrony adalah konsep yang sangat luas dan dapat dicapai dengan banyak cara.
Berikut ini dapat dibedakan dalam sejarah .NET :
- EAP (Pola Asinkron Berbasis Kejadian) - sesuai namanya, kenaikan ini didasarkan pada peristiwa yang terjadi saat operasi selesai dan metode biasa yang menyebut operasi ini
- APM (Asynchronous Programming Model) - berdasarkan 2 metode. Metode BeginSmth mengembalikan antarmuka IAsyncResult. Metode EndSmth menerima IAsyncResult (jika operasi tidak selesai pada saat EndSmth dipanggil, utas diblokir)
- TAP (Pola Asinkron Berbasis Tugas) adalah async / menunggu yang sama (sebenarnya, kata-kata ini muncul setelah pendekatan dan jenis Tugas dan Tugas <TResult> muncul, tetapi async / menunggu secara signifikan meningkatkan konsep ini)
Pendekatan terakhir begitu sukses sehingga semua orang berhasil melupakan yang sebelumnya. Jadi, ini tentang dia.
Pola asinkron berbasis tugas. Sintaks dan Kondisi Kompilasi
Metode asinkron gaya TAP standar sangat mudah untuk ditulis.
Untuk melakukan ini, Anda perlu :
- Untuk nilai pengembalian menjadi Tugas, Tugas <T>, atau batal (tidak disarankan, dibahas nanti). Di C # 7 datang jenis-jenis Tugas (dibahas dalam bab terakhir). Dalam C # 8, IAsyncEnumerable <T> dan IAsyncEnumerator <T> ditambahkan ke daftar ini.
- Sehingga metode tersebut ditandai dengan kata kunci async dan berisi menunggu di dalam. Kata kunci ini dipasangkan. Selain itu, jika metode berisi menunggu, pastikan untuk menandainya async, kebalikannya tidak benar, tetapi tidak ada gunanya
- Untuk kesopanan, patuhi konvensi suffix Async. Tentu saja, kompiler tidak akan menganggap ini sebagai kesalahan. Jika Anda adalah pengembang yang sangat baik, Anda dapat menambahkan kelebihan dengan PembatalanToken (dibahas pada bab terakhir)
Untuk metode seperti itu, kompiler melakukan pekerjaan yang serius. Dan mereka menjadi benar-benar tidak dapat dikenali di balik layar, tetapi lebih pada itu nanti.
Disebutkan bahwa metode tersebut harus mengandung kata kunci yang menunggu. Ini (kata) menunjukkan perlunya asynchronous menunggu tugas yang akan dilakukan, yang merupakan objek tugas yang diterapkan.
Objek tugas juga memiliki kondisi tertentu sehingga menunggu dapat diterapkan padanya:- Jenis yang diharapkan harus memiliki metode GetAwaiter () publik (atau internal), juga bisa menjadi metode ekstensi. Metode ini mengembalikan objek tunggu.
- Objek menunggu harus mengimplementasikan antarmuka INotifyCompletion, yang memerlukan implementasi metode OnCompleted (Aksi lanjutan) void. Itu juga harus memiliki properti instance bool IsCompleted, void GetResult () method. Ini bisa berupa struktur atau kelas.
Contoh di bawah ini menunjukkan cara membuat int diharapkan, dan bahkan tidak pernah dieksekusi.
Ekstensi intpublic class Program { public static async Task Main() { await 1; } } public static class WeirdExtensions { public static AnyTypeAwaiter GetAwaiter(this int number) => new AnyTypeAwaiter(); public class AnyTypeAwaiter : INotifyCompletion { public bool IsCompleted => false; public void OnCompleted(Action continuation) { } public void GetResult() { } } }
Bekerja dengan TAP
Sulit untuk pergi ke hutan tanpa memahami bagaimana sesuatu seharusnya bekerja. Pertimbangkan TAP dalam hal perilaku program.
Dalam terminologi: metode asinkron yang dimaksud, yang kodenya akan dipertimbangkan, saya akan memanggil
metode asinkron , dan metode asinkron yang disebut di dalamnya saya akan memanggil
operasi asinkron .
Mari kita ambil contoh paling sederhana, sebagai operasi asinkron, kita mengambil Task.Delay, yang menunda untuk waktu yang ditentukan tanpa memblokir aliran.
public static async Task DelayOperationAsync()
Eksekusi metode dalam hal perilaku adalah sebagai berikut.
- Semua kode yang mendahului doa operasi asinkron dijalankan. Dalam hal ini, ini adalah metode BeforeCall
- Panggilan operasi asinkron sedang berlangsung. Pada tahap ini, utas tidak dibebaskan atau diblokir. Operasi ini mengembalikan hasil - objek tugas yang disebutkan (biasanya Tugas), yang disimpan dalam variabel lokal
- Kode dijalankan setelah memanggil operasi asinkron, tetapi sebelum menunggu (menunggu). Dalam contohnya - AfterCall
- Menunggu penyelesaian pada objek tugas (yang disimpan dalam variabel lokal) - tunggu tugas.
Jika operasi asinkron selesai pada titik ini, maka eksekusi berlanjut secara sinkron, di utas yang sama.
Jika operasi asinkron tidak selesai, maka kode disimpan yang harus dipanggil setelah penyelesaian operasi asinkron (yang disebut kelanjutan), dan aliran kembali ke kumpulan utas dan menjadi tersedia untuk digunakan. - Eksekusi operasi setelah menunggu - AfterAwait - dilakukan segera, di utas yang sama, ketika operasi pada saat menunggu selesai, atau, setelah selesainya operasi, utas baru diambil yang akan dilanjutkan (disimpan pada langkah sebelumnya)
Di belakang layar. Mesin negara
Bahkan, metode kami ditransformasikan oleh kompiler menjadi metode rintisan di mana kelas yang dihasilkan - mesin negara - diinisialisasi. Kemudian (mesin) dimulai, dan objek Tugas yang digunakan pada langkah 2 dikembalikan dari metode.
Yang menarik adalah metode
MoveNext mesin negara. Metode ini melakukan apa sebelum konversi dalam metode asinkron. Itu memecahkan kode antara setiap panggilan tunggu. Setiap bagian dilakukan dalam kondisi mesin tertentu. Metode
MoveNext itu sendiri dilampirkan ke objek tunggu sebagai kelanjutan. Pelestarian negara menjamin pelaksanaan bagian itu yang secara logis mengikuti harapan.
Seperti yang mereka katakan, lebih baik melihat 1 kali daripada mendengar 100 kali, jadi saya sangat menyarankan Anda membiasakan diri dengan contoh di bawah ini. Saya menulis ulang kode sedikit, meningkatkan penamaan variabel, dan berkomentar dengan murah hati.
Kode sumber public static async Task Delays() { Console.WriteLine(1); await Task.Delay(1000); Console.WriteLine(2); await Task.Delay(1000); Console.WriteLine(3); await Task.Delay(1000); Console.WriteLine(4); await Task.Delay(1000); Console.WriteLine(5); await Task.Delay(1000); }
Metode rintisan [AsyncStateMachine(typeof(DelaysStateMachine))] [DebuggerStepThrough] public Task Delays() { DelaysStateMachine stateMachine = new DelaysStateMachine(); stateMachine.taskMethodBuilder = AsyncTaskMethodBuilder.Create(); stateMachine.currentState = -1; AsyncTaskMethodBuilder builder = stateMachine.taskMethodBuilder; taskMethodBuilder.Start(ref stateMachine); return stateMachine.taskMethodBuilder.Task; }
Mesin negara [CompilerGenerated] private sealed class DelaysStateMachine : IAsyncStateMachine {
Saya fokus pada frasa "pada saat ini belum dijalankan secara serempak." Operasi asinkron juga dapat mengikuti jalur eksekusi yang sinkron. Kondisi utama untuk metode asinkron saat ini untuk dieksekusi secara serempak, yaitu, tanpa mengubah utas, adalah penyelesaian operasi asinkron pada saat verifikasi
IsCompleted .
Contoh ini dengan jelas menunjukkan perilaku ini. static async Task Main() { Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
Tentang konteks sinkronisasi. Metode
AwaitUnsafeOnCompleted yang digunakan dalam mesin pada akhirnya menghasilkan panggilan ke metode
Task.SetContinuationForAwait . Dalam metode ini, konteks sinkronisasi saat ini
SynchronizationContext.Current diambil. Konteks sinkronisasi dapat diartikan sebagai jenis aliran. Jika juga spesifik (misalnya, konteks utas UI), kelanjutan dibuat menggunakan kelas
SynchronizationContextAwaitTaskContinuation . Kelas ini untuk memulai kelanjutan memanggil metode Posting pada konteks yang disimpan, yang memastikan bahwa kelanjutan dieksekusi dalam konteks yang tepat di mana metode dijalankan. Logika khusus untuk mengeksekusi kelanjutan tergantung pada metode
Post dalam konteks yang, secara sederhana, tidak dikenal untuk kecepatan. Jika tidak ada konteks sinkronisasi (atau ditunjukkan bahwa itu tidak masalah bagi kami di mana konteks eksekusi akan terus menggunakan ConfigureAwait (false), yang akan dibahas pada bab terakhir), kelanjutan akan dilakukan oleh utas dari kumpulan.
Asal-usul asynchrony. Metode asinkron standar perangkat
Kami melihat bagaimana metode menggunakan async dan menunggu penampilan dan apa yang terjadi di balik layar. Informasi ini tidak jarang. Tetapi penting untuk memahami sifat operasi asinkron. Karena, seperti yang kita lihat di mesin negara, operasi asinkron dipanggil dalam kode, kecuali hasilnya diproses lebih licik. Namun, apa yang terjadi di dalam operasi asinkron itu sendiri? Mungkin sama, tetapi ini tidak dapat terjadi tanpa batas.
Tugas penting adalah memahami sifat asinkron. Ketika mencoba memahami asinkron, ada pergantian status "sekarang jelas" dan "sekarang lagi tidak bisa dipahami." Dan pergantian ini akan sampai sumber asynchrony dipahami.
Saat bekerja dengan asinkron, kami beroperasi pada tugas. Ini sama sekali tidak sama dengan aliran. Satu tugas dapat dilakukan oleh banyak utas, dan satu utas dapat melakukan banyak tugas.
Asynchrony biasanya dimulai dengan metode yang mengembalikan Task (misalnya), tetapi tidak ditandai dengan async, dan karenanya tidak menggunakan menunggu di dalam. Metode ini tidak mentolerir perubahan kompiler, tetapi dieksekusi apa adanya.
Jadi, mari kita lihat beberapa akar asynchrony.- Task.Run, Task baru (..). Mulai (), Factory.StartNew dan sejenisnya. Cara termudah untuk memulai eksekusi asinkron. Metode ini hanya membuat objek tugas baru, melewati delegasi sebagai salah satu parameter. Tugas ditransfer ke penjadwal, yang memberikannya untuk dieksekusi oleh salah satu utas di kumpulan. Tugas selesai yang dapat diharapkan dikembalikan. Biasanya, pendekatan ini digunakan untuk memulai komputasi (terikat CPU) di utas terpisah.
- TaskCompletionSource. Kelas pembantu yang membantu mengontrol objek tugas. Dirancang untuk mereka yang tidak dapat mengalokasikan delegasi untuk implementasi dan menggunakan mekanisme yang lebih canggih untuk mengendalikan penyelesaian. Ini memiliki API yang sangat sederhana - SetResult, SetError, dll., Yang memperbarui tugas sesuai. Tugas ini tersedia melalui properti Tugas. Mungkin di dalam Anda akan membuat utas, memiliki logika yang kompleks untuk interaksi mereka atau selesai dengan acara. Rincian lebih lanjut tentang kelas ini akan ada di bagian terakhir.
Dalam paragraf tambahan, Anda bisa membuat metode perpustakaan standar. Ini termasuk membaca / menulis file, bekerja dengan jaringan, dan sejenisnya. Sebagai aturan, metode populer dan umum tersebut menggunakan panggilan sistem yang bervariasi pada platform yang berbeda, dan perangkat mereka sangat menghibur. Pertimbangkan bekerja dengan file dan jaringan.
File
Catatan penting - jika Anda ingin bekerja dengan file, Anda harus menentukan useAsync = true saat membuat FileStream.
Semuanya diatur dalam file non-sepele dan membingungkan. Kelas FileStream dinyatakan parsial. Dan selain itu ada 6 add-on khusus platform lainnya. Jadi, di Unix, akses asinkron ke file sewenang-wenang, sebagai suatu peraturan, menggunakan operasi sinkron di utas terpisah. Di Windows ada panggilan sistem untuk operasi asinkron, yang tentu saja digunakan. Ini menyebabkan perbedaan dalam pekerjaan pada platform yang berbeda.
Sumber .
UnixPerilaku standar saat menulis atau membaca adalah melakukan operasi secara serempak, jika buffer memungkinkan dan aliran tidak sibuk dengan operasi lain:
1. Stream tidak sibuk dengan operasi lain
Kelas Filestream memiliki objek yang diwarisi dari SemaphoreSlim dengan parameter (1, 1) - yaitu, bagian kritis - fragmen kode yang dilindungi oleh semaphore ini dapat dieksekusi oleh hanya satu utas pada satu waktu. Semaphore ini digunakan untuk membaca dan menulis. Artinya, tidak mungkin menghasilkan sekaligus membaca dan menulis. Dalam hal ini, pemblokiran pada semaphore tidak terjadi. Metode this._asyncState.WaitAsync () dipanggil, yang mengembalikan objek tugas (tidak ada kunci atau menunggu, itu akan terjadi jika kata kunci tunggu diterapkan pada hasil metode). Jika objek tugas ini tidak selesai - yaitu, semaphore ditangkap, maka kelanjutan (Task.ContinueWith) di mana operasi dilakukan dilampirkan ke objek menunggu yang dikembalikan. Jika objek tersebut gratis, maka Anda perlu memeriksa yang berikut ini
2. Buffer memungkinkan
Di sini perilaku sudah tergantung pada sifat operasi.
Untuk merekam - diperiksa bahwa ukuran data untuk menulis + posisi dalam file kurang dari ukuran buffer, yang secara default adalah 4096 byte. Artinya, kita harus menulis 4096 byte dari awal, 2048 byte dengan offset 2048, dan seterusnya. Jika demikian, maka operasi dilakukan secara serempak, jika tidak kelanjutannya dilampirkan (Tugas.Lanjutkan dengan). Sekuelnya menggunakan panggilan sistem sinkron biasa. Ketika buffer penuh, itu ditulis ke disk secara sinkron.
Untuk membaca - diperiksa apakah ada cukup data dalam buffer untuk mengembalikan semua data yang diperlukan. Jika tidak, maka, sekali lagi, kelanjutan (Task.ContinueWith) dengan panggilan sistem sinkron.
Ngomong-ngomong, ada detail yang menarik. Jika satu bagian data menempati seluruh buffer, mereka akan ditulis langsung ke file, tanpa partisipasi buffer. Pada saat yang sama, ada situasi di mana akan ada lebih banyak data daripada ukuran buffer, tetapi mereka semua akan melewatinya. Ini terjadi jika sudah ada sesuatu di buffer. Kemudian data kita akan dibagi menjadi 2 bagian, satu akan mengisi buffer sampai akhir dan data akan ditulis ke file, yang kedua akan ditulis ke buffer jika masuk ke dalamnya atau langsung ke file jika tidak. Jadi, jika kita membuat stream dan menulis 4097 bytes, mereka akan segera muncul dalam file, tanpa memanggil Buang. Jika kita menulis 4095, maka tidak ada yang ada di file.
WindowsDi bawah Windows, algoritma untuk menggunakan buffer dan menulis secara langsung sangat mirip. Tetapi perbedaan yang signifikan diamati secara langsung dalam sistem asinkron menulis dan membaca panggilan. Berbicara tanpa masuk jauh ke panggilan sistem, ada struktur yang tumpang tindih. Ini memiliki bidang yang penting bagi kita - MENANGANInya. Ini adalah acara reset manual yang masuk ke kondisi alarm setelah operasi selesai. Kembali ke implementasi. Menulis secara langsung, serta menulis ke buffer, menggunakan pemanggilan sistem asinkron, yang menggunakan struktur di atas sebagai parameter. Saat merekam, objek FileStreamCompletionSource dibuat - pewaris TaskCompletionSource, di mana IOCallback ditentukan. Disebut dengan utas gratis dari kumpulan ketika operasi selesai. Dalam panggilan balik, struktur Tumpang tindih diuraikan dan objek Tugas diperbarui sesuai. Itu semua ajaib.
Jaringan
Sulit untuk menggambarkan segala sesuatu yang saya lihat mengerti sumbernya. Path saya terletak dari HttpClient ke Socket dan ke SocketAsyncContext untuk Unix. Skema umum sama dengan file. Untuk Windows, struktur Tumpang tindih yang disebutkan digunakan dan operasi dilakukan secara tidak sinkron. Di Unix, operasi jaringan juga menggunakan fungsi panggilan balik.
Dan sedikit penjelasan. Pembaca yang penuh perhatian akan melihat bahwa ketika menggunakan panggilan tidak sinkron antara panggilan dan panggilan balik, ada kekosongan tertentu yang entah bagaimana bekerja dengan data. Di sini perlu dijelaskan untuk kelengkapannya. Pada contoh file, disk controller melakukan operasi langsung dengan disk oleh disk controller, dialah yang memberikan sinyal tentang memindahkan kepala ke sektor yang diinginkan, dll. Prosesor ini gratis saat ini. Komunikasi dengan disk terjadi melalui port input / output. Mereka menunjukkan jenis operasi, lokasi data pada disk, dll. Selanjutnya, pengontrol dan disk terlibat dalam operasi ini dan setelah menyelesaikan pekerjaan mereka menghasilkan interupsi. Karenanya, panggilan sistem asinkron hanya menyumbangkan informasi ke port input / output, sementara yang sinkron juga menunggu hasilnya, menempatkan aliran dalam status pemblokiran. Skema ini tidak berpura-pura akurat (bukan tentang artikel ini), tetapi memberikan pemahaman konseptual tentang pekerjaan tersebut.
Sekarang sifat prosesnya jelas. Tetapi seseorang mungkin bertanya, apa yang harus dilakukan dengan asinkron? Tidak mungkin untuk menulis async melalui metode selamanya.
Pertama-tama Aplikasi dapat dibuat sebagai layanan. Dalam hal ini, titik masuk - Utama - ditulis dari awal oleh Anda. Sampai saat ini, Main tidak dapat disinkronkan, dalam versi bahasa 7, fitur ini ditambahkan. Tapi itu tidak mengubah apa pun secara radikal, hanya kompiler yang menghasilkan Main biasa, dan dari asynchronous hanya dibuat metode statis, yang disebut Main dan penyelesaiannya diharapkan secara serempak. Jadi, kemungkinan besar Anda memiliki beberapa tindakan berumur panjang. Untuk beberapa alasan, pada saat ini, banyak orang mulai berpikir tentang cara membuat utas untuk bisnis ini: melalui Task, ThreadPool, atau Utas secara umum, karena harus ada perbedaan dalam sesuatu. Jawabannya sederhana - tentu saja Tugas. Jika Anda menggunakan pendekatan TAP, jangan mengganggu pembuatan utas manual. Ini mirip dengan menggunakan HttpClient untuk hampir semua permintaan, dan POST dilakukan secara independen melalui Socket.
Kedua Aplikasi web. Setiap permintaan masuk menyebabkan utas baru ditarik dari ThreadPool untuk diproses. Kolam, tentu saja, besar, tetapi tidak terbatas. Jika ada banyak permintaan, mungkin tidak ada utas sama sekali, dan semua permintaan baru akan antri untuk diproses. Situasi ini disebut kelaparan. Tetapi dalam kasus menggunakan pengontrol asinkron, seperti dibahas sebelumnya, aliran kembali ke kumpulan dan dapat digunakan untuk memproses permintaan baru. Dengan demikian, throughput server meningkat secara signifikan.
Kami melihat proses asinkron dari awal hingga akhir.
Dan dipersenjatai dengan pemahaman tentang semua asinkron ini, yang bertentangan dengan sifat manusia, kami akan mempertimbangkan beberapa trik yang berguna ketika bekerja dengan kode asinkron.Kelas dan trik yang berguna saat bekerja dengan TAP
Keragaman statis dari kelas Tugas.
Kelas Tugas memiliki beberapa metode statis yang berguna. Di bawah ini adalah yang utama.- Task.WhenAny (..) adalah kombinator yang mengambil IEnumerable / params dari objek tugas dan mengembalikan objek tugas yang akan selesai ketika tugas pertama yang selesai selesai. Artinya, ini memungkinkan Anda untuk menunggu salah satu dari beberapa tugas yang sedang berjalan
- Task.WhenAll (..) - kombinator, menerima IEnumerable / params dari objek tugas dan mengembalikan objek tugas, yang akan diselesaikan setelah menyelesaikan semua tugas yang ditransfer
- Task.FromResult<T>(T value) — , .
- Task.Delay(..) —
- Task.Yield() — . , . , ,
ConfigureAwait
Secara alami, fitur "lanjutan" paling populer. Metode ini milik kelas Tugas dan memungkinkan Anda menentukan apakah kami perlu melanjutkan dalam konteks yang sama di mana operasi asinkron dipanggil. Secara default, tanpa menggunakan metode ini, konteks diingat dan dilanjutkan di dalamnya menggunakan metode Post yang disebutkan. Namun, seperti yang kami katakan, Post adalah kesenangan yang sangat mahal. Oleh karena itu, jika kinerjanya berada di tempat pertama, dan kami melihat bahwa kelanjutan tidak akan, katakanlah, memperbarui UI, Anda dapat menentukan .ConfigureAwait (false) pada objek yang menunggu . Ini berarti bahwa tidak masalah bagi kami di mana kelanjutan akan dilakukan.Sekarang tentang masalahnya. Seperti yang mereka katakan, menakutkan bukan ketidaktahuan, tetapi pengetahuan palsu.Entah bagaimana saya mengamati kode aplikasi web, di mana setiap panggilan asinkron didekorasi dengan akselerator ini. Ini tidak memiliki efek selain jijik visual. Aplikasi web standar ASP.NET Core tidak memiliki konteks unik (kecuali jika Anda sendiri yang menulisnya). Dengan demikian, metode Post tidak dipanggil di sana.TaskCompletionSource <T>
Kelas yang memudahkan mengelola objek Tugas. Kelas memiliki banyak peluang, tetapi paling berguna ketika kita ingin menyelesaikan tugas dengan tindakan, yang akhirnya terjadi pada suatu peristiwa. Secara umum, kelas diciptakan untuk mengadaptasi metode asinkron lama ke TAP, tetapi seperti yang telah kita lihat, ini digunakan tidak hanya untuk ini. Contoh kecil bekerja dengan kelas ini:Contoh public static Task<string> GetSomeDataAsync() { TaskCompletionSource<string> tcs = new TaskCompletionSource<string>(); FileSystemWatcher watcher = new FileSystemWatcher { Path = Directory.GetCurrentDirectory(), NotifyFilter = NotifyFilters.LastAccess, EnableRaisingEvents = true }; watcher.Changed += (o, e) => tcs.SetResult(e.FullPath); return tcs.Task; }
Kelas ini membuat pembungkus asinkron untuk mendapatkan nama file yang diakses di folder saat ini.PembatalanTokenSource
Memungkinkan Anda membatalkan operasi asinkron. Garis besar umum menyerupai penggunaan TaskCompletionSource. Pertama, var cts = new PembatalanTokenSource () dibuat , yang, omong-omong, IDisposable, kemudian cts.Token dilewatkan ke operasi asinkron . Selanjutnya, mengikuti beberapa logika Anda, dalam kondisi tertentu, metode cts.Cancel () dipanggil . Itu juga dapat berlangganan ke suatu acara atau apa pun.Menggunakan PembatalanToken adalah praktik yang baik. Saat menulis metode asinkron Anda yang berfungsi di latar belakang, katakan dalam waktu tak terbatas, Anda cukup memasukkan satu baris ke badan loop: cancellationToken.ThrowIfCancellationRequested () , yang akan mengeluarkan pengecualianOperationCanceledException . Pengecualian ini diperlakukan sebagai pembatalan operasi dan tidak disimpan sebagai pengecualian di dalam objek tugas. Juga, properti IsCanceled pada objek Tugas akan menjadi benar.Longrunning
, , , . , . ( ), . :
Task.Factory.StartNew(action, TaskCreationOptions.LongRunning )Task.Factory.StartNew , .
Karena sifat non-deterministik dari eksekusi kode asinkron, pertanyaan tentang pengecualian sangat relevan. Akan memalukan jika Anda tidak bisa menangkap pengecualian dan itu dilemparkan ke utas kiri, membunuh proses. Kelas ExceptionDispatchInfo dibuat untuk menangkap pengecualian dalam satu utas dan melemparkannya ke dalamnya . Untuk menangkap pengecualian, metode statis ExceptionDispatchInfo.Capture (ex) digunakan, yang mengembalikan ExceptionDispatchInfo.Tautan ke objek ini dapat diteruskan ke utas apa pun, yang kemudian memanggil metode Throw () untuk membuangnya. Lemparan itu sendiri TIDAK terjadi di tempat panggilan operasi tidak sinkron, tetapi di tempat penggunaan operator yang menunggu. Dan seperti yang Anda tahu, menunggu tidak dapat diterapkan untuk membatalkan. Jadi, jika konteksnya ada, itu akan diteruskan dengan metode Post. Kalau tidak, itu akan bersemangat dalam aliran dari kolam. Dan ini hampir 100% halo pada runtuhnya aplikasi. Dan di sini kita sampai pada praktik fakta bahwa kita harus menggunakan Tugas atau Tugas <T>, tetapi tidak batal.Dan satu hal lagi. Penjadwal memiliki tugas TaskScheduler.UnobservedTaskException yang menyala ketika UnobservedTaskException dilempar. Pengecualian ini dilemparkan selama pengumpulan sampah ketika GC mencoba untuk mengumpulkan objek tugas yang memiliki pengecualian tidak tertangani.IAsyncEnumerable
Sebelum C # 8 dan .NET Core 3.0, tidak mungkin untuk menggunakan iterator hasil dalam metode asinkron, yang mempersulit masa pakai dan membuatnya mengembalikan Tugas <IEnumerable <T>> dari metode ini, mis. tidak ada cara untuk mengulangi koleksi sampai diterima sepenuhnya. Sekarang ada kesempatan seperti itu. Pelajari lebih lanjut di sini . Untuk ini, tipe pengembalian harus IAsyncEnumerable <T> (atau IAsyncEnumerator <T> ). Untuk melintasi koleksi seperti itu, Anda harus menggunakan foreach loop dengan kata kunci yang menunggu. Juga, metode WithCancellation dan ConfigureAwait dapat dipanggil pada hasil operasi , yang menunjukkan CancelationToken yang digunakan dan kebutuhan untuk melanjutkan dalam konteks yang sama.Seperti yang diharapkan, semuanya dilakukan dengan malas mungkin.Di bawah ini adalah contoh dan kesimpulan yang dia berikan.Contoh public class Program { public static async Task Main() { Stopwatch sw = new Stopwatch(); sw.Start(); IAsyncEnumerable<int> enumerable = AsyncYielding(); Console.WriteLine($"Time after calling: {sw.ElapsedMilliseconds}"); await foreach (var element in enumerable.WithCancellation(..).ConfigureAwait(false)) { Console.WriteLine($"element: {element}"); Console.WriteLine($"Time: {sw.ElapsedMilliseconds}"); } } static async IAsyncEnumerable<int> AsyncYielding() { foreach (var uselessElement in Enumerable.Range(1, 3)) { Task task = Task.Delay(TimeSpan.FromSeconds(uselessElement)); Console.WriteLine($"Task run: {uselessElement}"); await task; yield return uselessElement; } } }
Kesimpulan:Waktu setelah panggilan: 0
Tugas dijalankan: 1
elemen: 1
Waktu: 1033
Tugas dijalankan: 2
elemen: 2
Waktu: 3034
Tugas dijalankan: 3
elemen: 3
Waktu: 6035Threadpool
Kelas ini digunakan secara aktif saat pemrograman dengan TAP. Karena itu, saya akan memberikan detail minimum implementasinya. Di dalam, ThreadPool memiliki array antrian: satu untuk setiap utas + satu global. Saat menambahkan pekerjaan baru ke kumpulan, utas yang memulai penambahan diperhitungkan. Dalam hal ini adalah utas dari kumpulan, karya diletakkan dalam antrian sendiri utas ini, jika itu adalah utas lain - di utas global. Ketika utas dipilih untuk berfungsi, antrian lokalnya pertama-tama terlihat. Jika kosong, utas mengambil pekerjaan dari global. Jika kosong, ia mulai mencuri dari yang lain. Juga, Anda tidak boleh mengandalkan urutan pekerjaan, karena, pada kenyataannya, tidak ada pesanan. Jumlah utas default dalam kumpulan bergantung pada banyak faktor, termasuk ukuran ruang alamat. Jika ada lebih banyak permintaan untuk eksekusi,dari jumlah utas yang tersedia, permintaan diantri.Utas di kumpulan utas adalah utas latar (properti isBackground = true). Jenis utas ini tidak mendukung umur proses jika semua utas depan telah selesai.Utas sistem memantau status pegangan tunggu. Ketika operasi tunggu berakhir, callback yang ditransfer dijalankan oleh utas dari kelompok (ingat file di Windows).Jenis tugas-seperti
Disebutkan sebelumnya, tipe ini (struktur atau kelas) dapat digunakan sebagai nilai balik dari metode asinkron. Tipe builder harus dikaitkan dengan tipe ini menggunakan atribut [AsyncMethodBuilder (..)] . Jenis ini harus memiliki karakteristik yang disebutkan di atas agar dapat menerapkan kata kunci yang menunggu untuk itu. Itu dapat diparameterisasi untuk metode yang tidak mengembalikan nilai dan parameter untuk yang kembali.Builder itu sendiri adalah kelas atau struktur yang kerangka kerjanya ditunjukkan pada contoh di bawah ini. Metode SetResult memiliki parameter tipe T untuk tipe tugas seperti yang diparameterisasi oleh T. Untuk tipe non-parameter, metode ini tidak memiliki parameter.Antarmuka Builder yang Dibutuhkan class MyTaskMethodBuilder<T> { public static MyTaskMethodBuilder<T> Create(); public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine; public void SetStateMachine(IAsyncStateMachine stateMachine); public void SetException(Exception exception); public void SetResult(T result); public void AwaitOnCompleted<TAwaiter, TStateMachine>( ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine; public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>( ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine; public MyTask<T> Task { get; } }
Prinsip kerja dari sudut pandang penulisan jenis Tugas Anda akan dijelaskan di bawah ini. Sebagian besar sudah dijelaskan ketika menguraikan kode yang dihasilkan oleh kompiler.Kompiler menggunakan semua jenis ini untuk menghasilkan mesin negara. Kompiler tahu pembangun mana yang akan digunakan untuk jenis yang diketahuinya, di sini kami menentukan apa yang akan digunakan selama pembuatan kode. Jika mesin keadaan adalah struktur, maka itu akan dikemas saat memanggil SetStateMachine , pembangun dapat menyimpan salinan yang dikemas jika perlu. Pembangun harus memanggil stateMachine.MoveNext dalam metode Mulai atau setelah dipanggil untuk memulai eksekusi dan memajukan mesin negara. Setelah memanggil Mulai, properti Task akan dikembalikan dari metode. Saya sarankan Anda kembali ke metode rintisan dan melihat langkah-langkah ini.Jika mesin negara selesai dengan sukses, metode SetResult dipanggil , jika tidak, SetException . Jika mesin keadaan mencapai menunggu, metode GetAwaiter () dari jenis seperti tugas dijalankan . Jika objek menunggu mengimplementasikan antarmuka ICriticalNotifyCompletion dan IsCompleted = false, mesin state menggunakan builder.AwaitUnsafeOnCompleted (ref awaiter, ref stateMachine) . Metode AwaitUnsafeOnCompleted harus memanggil awaiter.OnCompleted (tindakan) , tindakan tersebut harus memanggil stateMachine.MoveNextketika objek menunggu selesai. Demikian pula untuk antarmuka INotifyCompletion dan metode builder.AwaitOnCompleted .Cara menggunakan ini terserah Anda. Tetapi saya menyarankan Anda untuk memikirkan 514 kali sebelum menerapkan ini dalam produksi, dan bukan untuk memanjakan. Berikut ini adalah contoh penggunaan. Saya membuat sketsa hanya proksi untuk pembangun standar yang menampilkan ke konsol metode mana yang dipanggil dan pada jam berapa. Omong-omong, Main asynchronous () tidak ingin mendukung jenis ekspektasi khusus (saya percaya bahwa lebih dari satu proyek produksi rusak karena putus asa oleh Microsoft). Jika mau, Anda dapat memodifikasi logger proksi menggunakan logger normal dan mencatat lebih banyak informasi.Tugas Proksi Pencatatan public class Program { public static void Main() { Console.WriteLine("Start"); JustMethod().Task.Wait();
Kesimpulan:Mulai
Metode: Buat; 2019-10-09T17: 55: 13.7152733 + 03: 00
Metode: Mulai; 2019-10-09T17: 55: 13.7262226 + 03: 00
Metode: AwaitUnsafeOnCompleted; 2019-10-09T17: 55: 13.7275206 + 03: 00
Properti: Tugas; 2019-10-09T17: 55: 13.7292005 + 03: 00
Metode: SetResult; 2019-10-09T17: 55: 14.7297967 + 03:00
StopItu saja, terima kasih semuanya.