Saya menggunakan ConcurrentQueue
untuk struktur data bersama yang tujuannya adalah menahan objek N terakhir yang diteruskan ke sana (jenis riwayat).
Asumsikan kita memiliki browser dan ingin memiliki 100 URL terakhir yang diramban. Saya ingin antrian yang secara otomatis menjatuhkan (dequeue) entri terlama (pertama) pada penyisipan entri baru (enqueue) ketika kapasitas penuh (100 alamat dalam riwayat).
Bagaimana saya bisa melakukannya dengan menggunakan System.Collections
?
Jawaban:
Saya akan menulis kelas pembungkus yang di Enqueue akan memeriksa Hitungan dan kemudian Dequeue ketika hitungan melebihi batas.
sumber
q
bersifat pribadi untuk objek, sehinggalock
akan mencegah utas lain mengakses secara bersamaan.Count
danTryDequeue
merupakan dua operasi independen yang tidak disinkronkan oleh BCL Bersamaan.ConcurrentQueue<T>
objek denganQueue<T>
objek yang lebih ringan.Enqueue
masih akan memanggil antrian asli. Dengan kata lain, meskipun jawaban ini ditandai sebagai diterima, namun benar-benar rusak.Saya akan memilih sedikit varian ... memperpanjang ConcurrentQueue agar dapat menggunakan ekstensi Linq di FixedSizeQueue
sumber
Bagi siapa saja yang merasa berguna, berikut adalah beberapa kode kerja berdasarkan jawaban Richard Schneider di atas:
sumber
Untuk apa nilainya, berikut adalah buffer melingkar ringan dengan beberapa metode yang ditandai untuk penggunaan yang aman dan tidak aman.
Saya suka menggunakan
Foo()/SafeFoo()/UnsafeFoo()
konvensi:Foo
metode panggilanUnsafeFoo
sebagai default.UnsafeFoo
metode mengubah status secara bebas tanpa kunci, mereka hanya memanggil metode tidak aman lainnya.SafeFoo
metode memanggilUnsafeFoo
metode di dalam kunci.Ini sedikit bertele-tele, tetapi itu membuat kesalahan yang jelas, seperti memanggil metode yang tidak aman di luar kunci dalam metode yang seharusnya aman untuk thread, lebih jelas.
sumber
Inilah pendapat saya tentang Antrian ukuran tetap
Ini menggunakan Queue biasa, untuk menghindari overhead sinkronisasi saat
Count
properti digunakanConcurrentQueue
. Ini juga mengimplementasikanIReadOnlyCollection
sehingga metode LINQ dapat digunakan. Sisanya sangat mirip dengan jawaban lain di sini.sumber
Hanya untuk bersenang-senang, berikut adalah implementasi lain yang saya yakin dapat menjawab sebagian besar kekhawatiran para pemberi komentar. Secara khusus, keamanan thread dicapai tanpa penguncian dan implementasinya disembunyikan oleh kelas pembungkus.
sumber
_queue.Enqueue(obj)
tetapi sebelumnyaInterlocked.Increment(ref _count)
, dan utas lainnya memanggil.Count
? Itu akan salah menghitung. Saya belum memeriksa masalah lainnya.Versi saya hanyalah subclass dari yang normal
Queue
.. tidak ada yang istimewa kecuali melihat semua orang berpartisipasi dan masih sesuai dengan judul topik saya sebaiknya taruh di sini. Itu juga mengembalikan yang dequeue untuk berjaga-jaga.sumber
Mari tambahkan satu jawaban lagi. Mengapa ini terjadi pada orang lain?
1) Kesederhanaan. Mencoba menjamin ukuran itu baik dan bagus tetapi mengarah pada kerumitan yang tidak diperlukan yang dapat menunjukkan masalahnya sendiri.
2) Menerapkan IReadOnlyCollection, artinya Anda dapat menggunakan Linq di atasnya dan menyebarkannya ke berbagai hal yang diharapkan IEnumerable.
3) Tidak ada penguncian. Banyak solusi di atas menggunakan kunci, yang salah pada koleksi tanpa kunci.
4) Mengimplementasikan kumpulan metode, properti, dan antarmuka yang sama dengan yang dilakukan ConcurrentQueue, termasuk IProducerConsumerCollection, yang penting jika Anda ingin menggunakan koleksi dengan BlockingCollection.
Implementasi ini berpotensi berakhir dengan lebih banyak entri daripada yang diharapkan jika TryDequeue gagal, tetapi frekuensi kemunculannya tampaknya tidak sebanding dengan kode khusus yang pasti akan menghambat kinerja dan menyebabkan masalah tak terduga sendiri.
Jika Anda benar-benar ingin menjamin suatu ukuran, menerapkan Prune () atau metode serupa sepertinya merupakan ide terbaik. Anda dapat menggunakan kunci baca ReaderWriterLockSlim dengan metode lain (termasuk TryDequeue) dan mengambil kunci tulis hanya saat memangkas.
sumber
Hanya karena belum ada yang mengatakannya .. Anda dapat menggunakan
LinkedList<T>
dan menambahkan pengaman utas:Satu hal yang perlu diperhatikan adalah urutan enumerasi default adalah LIFO dalam contoh ini. Tapi itu bisa diganti jika perlu.
sumber
Untuk kesenangan coding Anda, saya kirimkan kepada Anda '
ConcurrentDeck
'Contoh Penggunaan:
sumber
Itu tergantung pada penggunaan. Saya perhatikan bahwa beberapa solusi di atas dapat melebihi ukuran bila digunakan dalam lingkungan multip-threaded. Pokoknya kasus penggunaan saya adalah untuk menampilkan 5 kejadian terakhir dan ada beberapa utas yang menulis kejadian ke dalam antrian dan satu utas lainnya membaca darinya dan menampilkannya dalam Kontrol Winform. Jadi inilah solusi saya.
EDIT: Karena kami sudah menggunakan penguncian dalam implementasi kami, kami tidak benar-benar membutuhkan ConcurrentQueue itu dapat meningkatkan kinerja.
EDIT: Kami tidak terlalu membutuhkan
syncObject
contoh di atas dan kami lebih suka menggunakanqueue
objek karena kami tidak menginisialisasi ulangqueue
dalam fungsi apa pun dan itu ditandai sebagaireadonly
.sumber
Jawaban yang diterima akan memiliki efek samping yang dapat dihindari.
Link di bawah ini adalah referensi yang saya gunakan ketika saya menulis contoh saya di bawah ini.
Meskipun dokumentasi dari Microsoft agak menyesatkan karena mereka menggunakan kunci, namun mereka mengunci kelas segmen. Kelas segmen itu sendiri menggunakan Interlocked.
sumber
Berikut adalah implementasi lain yang menggunakan ConcurrentQueue yang mendasari sebanyak mungkin sambil menyediakan antarmuka yang sama yang tersedia melalui ConcurrentQueue.
sumber
Ini adalah versi antrian saya:
Saya merasa berguna untuk memiliki konstruktor yang dibangun di atas IEnumerable dan saya merasa berguna memiliki GetSnapshot untuk memiliki daftar aman multithread (array dalam kasus ini) dari item pada saat panggilan, yang tidak naik kesalahan jika koleksi yang mendasari berubah.
Pemeriksaan Hitung ganda untuk mencegah kunci dalam beberapa keadaan.
sumber