Perlu memahami penggunaan SemaphoreSlim

90

Ini kode yang saya miliki tetapi saya tidak mengerti apa SemaphoreSlimyang saya lakukan.

async Task WorkerMainAsync()
{
    SemaphoreSlim ss = new SemaphoreSlim(10);
    List<Task> trackedTasks = new List<Task>();
    while (DoMore())
    {
        await ss.WaitAsync();
        trackedTasks.Add(Task.Run(() =>
        {
            DoPollingThenWorkAsync();
            ss.Release();
        }));
    }
    await Task.WhenAll(trackedTasks);
}

void DoPollingThenWorkAsync()
{
    var msg = Poll();
    if (msg != null)
    {
        Thread.Sleep(2000); // process the long running CPU-bound job
    }
}

Apa yang menunggu ss.WaitAsync();dan ss.Release();dilakukan?

Saya kira jika saya menjalankan 50 utas sekaligus kemudian menulis kode seperti SemaphoreSlim ss = new SemaphoreSlim(10);itu maka itu akan dipaksa untuk menjalankan 10 utas aktif sekaligus.

Ketika salah satu dari 10 utas selesai, utas lainnya akan dimulai. Jika saya tidak benar, bantu saya untuk memahami dengan situasi sampel.

Mengapa awaitdibutuhkan bersama ss.WaitAsync();? Apa yang ss.WaitAsync();dilakukannya?

Mou
sumber
3
Satu hal yang perlu diperhatikan adalah Anda benar-benar harus membungkus "DoPollingThenWorkAsync ();" dalam "coba {DoPollingThenWorkAsync ();} akhirnya {ss.Release ();}", jika tidak pengecualian akan membuat semafor itu kelaparan secara permanen.
Austin Salgat
Saya merasa agak aneh bahwa kami memperoleh dan melepaskan semaphore di luar / di dalam tugas masing-masing. Apakah memindahkan "await ss.WaitAsync ()" di dalam tugas akan membuat perbedaan?
Shane Lu

Jawaban:

73

saya rasa jika saya menjalankan 50 thread sekaligus maka kode seperti SemaphoreSlim ss = new SemaphoreSlim (10); akan memaksa untuk menjalankan 10 utas aktif sekaligus

Itu betul; penggunaan semaphore memastikan bahwa tidak akan ada lebih dari 10 pekerja yang melakukan pekerjaan ini pada waktu yang sama.

Memanggil WaitAsyncdi semaphore menghasilkan tugas yang akan diselesaikan ketika utas itu telah diberi "akses" ke token itu. await-ing tugas itu memungkinkan program melanjutkan eksekusi ketika "diizinkan" untuk melakukannya. Memiliki versi asinkron, daripada memanggil Wait, penting untuk memastikan bahwa metode tetap asinkron, bukan sinkron, serta berkaitan dengan fakta bahwa suatu asyncmetode dapat mengeksekusi kode di beberapa utas, karena callback, dan sebagainya. hubungan benang alami dengan semaphore bisa menjadi masalah.

Catatan tambahan: DoPollingThenWorkAsynctidak boleh memiliki Asyncpostfix karena sebenarnya tidak asinkron, melainkan sinkron. Sebut saja DoPollingThenWork. Ini akan mengurangi kebingungan bagi para pembaca.

Pelayanan
sumber
terima kasih tapi tolong beritahu saya apa yang terjadi ketika kami menentukan tidak ada utas yang akan dijalankan katakan 10. ketika salah satu dari 10 utas selesai, lalu lagi utas itu melompat ke menyelesaikan pekerjaan lain atau kembali ke pool? ini tidak terlalu jelas untuk .... jadi tolong jelaskan apa yang terjadi di balik layar.
Mou
@Mou Apa yang tidak jelas tentang itu? Kode menunggu hingga ada kurang dari 10 tugas yang sedang berjalan; bila ada, ia menambahkan yang lain. Ketika tugas selesai, ini menunjukkan bahwa itu telah selesai. Itu dia.
Pelayanan
apa keuntungan dari menentukan tidak ada utas yang akan dijalankan. jika terlalu banyak utas dapat menghambat kinerja? jika ya lalu mengapa menghambat ... jika saya menjalankan 50 utas alih-alih 10 utas lalu mengapa kinerja akan penting ... bisakah kamu jelaskan. terima kasih
Thomas
4
@ Thomas Jika Anda memiliki terlalu banyak utas bersamaan maka utas menghabiskan lebih banyak waktu untuk beralih konteks daripada yang mereka habiskan untuk melakukan pekerjaan produktif. Throughput turun saat thread naik saat Anda menghabiskan lebih banyak waktu untuk mengelola thread daripada melakukan pekerjaan, setidaknya, setelah jumlah thread Anda melewati jumlah inti pada mesin.
Pelayanan
3
@Servy Itu bagian dari tugas penjadwal tugas. Tasks! = Utas. Dalam Thread.Sleepkode asli akan merusak penjadwal tugas. Jika Anda tidak asinkron dengan intinya, Anda tidak asinkron.
Joseph Lennox
54

Di taman kanak-kanak di sudut, mereka menggunakan SemaphoreSlim untuk mengontrol berapa banyak anak yang dapat bermain di ruang olahraga.

Mereka mengecat di lantai, di luar ruangan, 5 pasang tapak kaki.

Saat anak-anak tiba, mereka meninggalkan sepatu dengan sepasang jejak kaki gratis dan memasuki ruangan.

Setelah mereka selesai bermain, mereka keluar, mengambil sepatu mereka dan "melepaskan" slot untuk anak lain.

Jika seorang anak datang dan tidak ada jejak kaki yang tersisa, mereka pergi bermain di tempat lain atau hanya tinggal sebentar dan memeriksa sesekali (mis., Tidak ada prioritas FIFO).

Ketika seorang guru ada di sekitar, dia "melepaskan" satu baris tambahan 5 jejak kaki di sisi lain koridor sehingga 5 anak lagi dapat bermain di ruangan itu pada waktu yang sama.

Ia juga memiliki "perangkap" yang sama dari SemaphoreSlim ...

Jika seorang anak selesai bermain dan meninggalkan ruangan tanpa mengambil sepatu (tidak memicu "pelepasan") maka slot tetap terhalang, meskipun secara teori ada slot kosong. Namun, anak itu biasanya dimarahi.

Kadang-kadang satu atau dua anak licik menyembunyikan sepatu mereka di tempat lain dan memasuki ruangan, bahkan jika semua jejak kaki sudah diambil (misalnya, SemaphoreSlim tidak "benar-benar" mengontrol berapa banyak anak di ruangan itu).

Ini biasanya tidak berakhir dengan baik, karena kepadatan ruangan cenderung berakhir dengan tangisan anak-anak dan guru menutup ruangan sepenuhnya.

dandiez
sumber
3
Jawaban seperti ini adalah favorit saya.
Stack Overfloweth
OMG ini informatif dan lucu sekali!
Zonus
7

Meskipun saya menerima pertanyaan ini benar-benar terkait dengan skenario kunci hitung mundur, saya pikir ada baiknya membagikan tautan ini yang saya temukan bagi mereka yang ingin menggunakan SemaphoreSlim sebagai kunci asinkron sederhana. Ini memungkinkan Anda untuk menggunakan pernyataan using yang dapat membuat pengkodean lebih rapi dan aman.

http://www.tomdupont.net/2016/03/how-to-release-semaphore-with-using.html

Saya melakukan swap _isDisposed=truedan _semaphore.Release()berkeliling di Buangnya meskipun jika itu entah bagaimana dipanggil beberapa kali.

Juga penting untuk dicatat SemaphoreSlim bukan kunci reentrant, artinya jika utas yang sama memanggil WaitAsync beberapa kali, jumlah semaphore berkurang setiap waktu. Singkatnya, SemaphoreSlim tidak menyadari Thread.

Mengenai kualitas kode pertanyaan, lebih baik meletakkan Rilis di akhir percobaan-akhirnya untuk memastikannya selalu dirilis.

andrew pate
sumber
6
Tidak disarankan untuk memposting jawaban hanya tautan karena tautan cenderung mati seiring waktu sehingga membuat jawaban tidak berharga. Jika Anda bisa, sebaiknya rangkum poin-poin penting atau blok kode kunci ke dalam jawaban Anda.
Yohanes