Mengapa Thread.Sleep sangat berbahaya

128

Saya sering melihatnya disebutkan bahwa Thread.Sleep();tidak boleh digunakan, tetapi saya tidak dapat memahami mengapa demikian. Jika Thread.Sleep();dapat menimbulkan masalah, apakah ada solusi alternatif dengan hasil yang sama yang aman?

misalnya.

while(true)
{
    doSomework();
    i++;
    Thread.Sleep(5000);
}

satu lagi adalah:

while (true)
{
    string[] images = Directory.GetFiles(@"C:\Dir", "*.png");

    foreach (string image in images)
    {
        this.Invoke(() => this.Enabled = true);
        pictureBox1.Image = new Bitmap(image);
        Thread.Sleep(1000);
    }
}
Burimi
sumber
3
Ringkasan blognya mungkin 'jangan menyalahgunakan Thread.sleep ()'.
Martin James
5
Saya tidak akan mengatakan itu berbahaya. Saya lebih suka mengatakan bahwa itu seperti goto:yaitu mungkin ada solusi yang lebih baik untuk masalah Anda daripada Sleep.
Default
9
Ini tidak persis sama goto, yang lebih seperti bau kode daripada bau desain. Tidak ada yang salah dengan kompiler yang memasukkan gotos ke dalam kode Anda: komputer tidak akan bingung. Tapi Thread.Sleeptidak persis sama; kompiler tidak memasukkan panggilan itu dan itu memiliki konsekuensi negatif lainnya. Tapi ya, sentimen umum yang menggunakannya salah karena hampir selalu ada solusi yang lebih baik pasti benar.
Cody Grey
32
Setiap orang memberikan pendapat tentang mengapa contoh di atas buruk, tetapi tidak ada yang menyediakan versi penulisan ulang yang tidak menggunakan Thread.Sleep () yang masih mencapai tujuan dari contoh yang diberikan.
StingyJack
3
Setiap kali Anda memasukkan sleep()kode (atau tes) anak anjing mati
Reza S

Jawaban:

163

Masalah dengan memanggil Thread.SleepAre menjelaskan cukup ringkas di sini :

Thread.Sleepmemiliki kegunaannya: mensimulasikan operasi yang panjang saat menguji / debugging pada thread MTA. Di .NET tidak ada alasan lain untuk menggunakannya.

Thread.Sleep(n)berarti memblokir utas saat ini setidaknya untuk jumlah potongan waktu (atau kuantum utas) yang dapat terjadi dalam n milidetik. Panjang kutu waktu berbeda pada versi / tipe Windows dan prosesor yang berbeda dan umumnya berkisar antara 15 hingga 30 milidetik. Ini berarti utas hampir dijamin akan diblokir selama lebih dari nmilidetik. Kemungkinan utas Anda akan aktif kembali tepat setelah nmilidetik hampir tidak mungkin dilakukan. Jadi, Thread.Sleeptidak ada gunanya mengatur waktu .

Untaian adalah sumber daya terbatas, mereka membutuhkan sekitar 200.000 siklus untuk dibuat dan sekitar 100.000 siklus untuk dihancurkan. Secara default, mereka mencadangkan 1 megabyte memori virtual untuk tumpukannya dan menggunakan 2.000-8.000 siklus untuk setiap sakelar konteks. Hal ini membuat setiap benang menunggu menjadi sangat sia - sia.

Solusi yang disukai: WaitHandles

Kesalahan yang paling banyak dibuat adalah menggunakan Thread.Sleep with a while-construct ( demo dan jawaban , entri blog yang bagus )

EDIT:
Saya ingin meningkatkan jawaban saya:

Kami memiliki 2 kasus penggunaan yang berbeda:

  1. Kami menunggu karena kami tahu rentang waktu tertentu kapan kami harus melanjutkan (gunakan Thread.Sleep , System.Threading.Timeratau serupa)

  2. Kami menunggu karena beberapa kondisi berubah sewaktu-waktu ... kata kunci adalah / are kadang kadang ! jika condition-check ada di code-domain kita, kita harus menggunakan WaitHandles - jika tidak, komponen eksternal harus menyediakan semacam pengait ... jika tidak, desainnya buruk!

Jawaban saya terutama mencakup kasus penggunaan 2

Andreas Niedermair
sumber
30
Saya tidak akan menyebut 1 MB memori sebagai pemborosan besar mengingat perangkat keras saat ini
Default
14
@Default hei, diskusikan dengan penulis asli :) dan, itu selalu tergantung pada kode Anda - atau lebih baik: faktor ... dan masalah utamanya adalah "Utas adalah sumber daya terbatas" - saat ini anak-anak tidak tahu banyak tentang efisiensi dan biaya implementasi tertentu, karena "perangkat keras itu murah" ... tetapi terkadang Anda perlu membuat kode yang sangat optd
Andreas Niedermair
11
'Ini membuat setiap utas menunggu menjadi sangat sia-sia' eh? Jika beberapa spesifikasi protokol menuntut jeda satu detik sebelum melanjutkan, apa yang akan menunggu 1 detik? Beberapa utas, di suatu tempat, harus menunggu! Overhead untuk pembuatan / penghancuran thread seringkali tidak relevan karena thread tetap harus dimunculkan untuk alasan lain dan tetap berjalan selama proses berlangsung. Saya akan tertarik untuk melihat cara apa pun untuk menghindari sakelar konteks ketika spesifikasi mengatakan 'setelah menyalakan pompa, tunggu setidaknya sepuluh detik agar tekanan stabil sebelum membuka katup umpan'.
Martin James
9
@CodyGray - Saya membaca posting lagi. Saya tidak melihat ikan dengan warna apa pun di komentar saya. Andreas menarik diri dari web: 'Thread.Sleep memiliki kegunaannya: mensimulasikan operasi yang panjang saat menguji / men-debug pada thread MTA. Di .NET tidak ada alasan lain untuk menggunakannya '. Saya berpendapat bahwa ada banyak aplikasi di mana panggilan sleep (), yah, hanya apa yang diperlukan. Jika leigons pengembang, (karena ada banyak), bersikeras menggunakan sleep () loop sebagai monitor kondisi yang harus diganti dengan event / condvars / semas / apapun, itu bukan pembenaran untuk pernyataan bahwa 'tidak ada alasan lain untuk menggunakannya '.
Martin James
8
Dalam 30 tahun pengembangan aplikasi multiThreaded, (kebanyakan C ++ / Delphi / Windows), saya tidak pernah melihat kebutuhan untuk loop sleep (0) atau sleep (1) dalam kode yang dapat dikirimkan. Kadang-kadang, saya memasukkan kode seperti itu untuk tujuan debugging, tetapi tidak pernah sampai ke pelanggan. 'jika Anda menulis sesuatu yang tidak sepenuhnya mengontrol setiap utas' - manajemen mikro utas adalah kesalahan besar seperti manajemen mikro staf pengembangan. Manajemen utas adalah tujuan OS - alat yang disediakannya harus digunakan.
Martin James
34

SKENARIO 1 - menunggu tugas asinkron selesai: Saya setuju bahwa WaitHandle / Auto | ManualResetEvent harus digunakan dalam skenario di mana utas menunggu tugas di utas lain selesai.

SKENARIO 2 - timing while loop: Namun, sebagai mekanisme timing yang kasar (while + Thread.Sleep) baik-baik saja untuk 99% aplikasi yang TIDAK memerlukan pengetahuan persis kapan Thread yang diblokir harus "aktif *. Argumen yang diperlukan 200k siklus untuk membuat utas juga tidak valid - utas pengulangan waktu perlu dibuat dan siklus 200k hanyalah angka besar lainnya (beri tahu saya berapa banyak siklus untuk membuka panggilan file / socket / db?).

Jadi jika sementara + Thread.Sleep berfungsi, mengapa mempersulit? Hanya pengacara sintaksis yang praktis !

Swab Jat
sumber
Untungnya kami sekarang memiliki TaskCompletionSource untuk menunggu hasil secara efisien.
Austin Salgat
14

Saya ingin menjawab pertanyaan ini dari perspektif politik-pengkodean, yang mungkin berguna atau tidak bagi siapa pun. Tetapi terutama ketika Anda berurusan dengan alat yang ditujukan untuk 9-5 programmer perusahaan, orang yang menulis dokumentasi cenderung menggunakan kata-kata seperti "tidak boleh" dan "tidak pernah" berarti "jangan lakukan ini kecuali Anda benar-benar tahu apa yang Anda sedang melakukan dan mengapa ".

Beberapa favorit saya yang lain di dunia C # adalah bahwa mereka memberitahu Anda untuk "jangan pernah menelepon kunci (ini)" atau "jangan pernah menelepon GC.Collect ()". Keduanya secara paksa dideklarasikan di banyak blog dan dokumentasi resmi, dan IMO adalah informasi yang salah. Pada tingkat tertentu, informasi yang salah ini memenuhi tujuannya, yaitu menjauhkan para pemula dari melakukan hal-hal yang tidak mereka pahami sebelum sepenuhnya meneliti alternatifnya, tetapi pada saat yang sama, itu membuat sulit untuk menemukan informasi NYATA melalui mesin pencari yang semuanya tampaknya mengarah ke artikel yang memberi tahu Anda untuk tidak melakukan sesuatu sementara tidak memberikan jawaban atas pertanyaan "mengapa tidak?"

Secara politis, ini bermuara pada apa yang orang anggap sebagai "desain yang baik" atau "desain yang buruk". Dokumentasi resmi tidak boleh mendikte desain aplikasi saya. Jika benar-benar ada alasan teknis bahwa Anda tidak boleh memanggil sleep (), maka IMO dokumentasi harus menyatakan bahwa tidak apa-apa untuk memanggilnya dalam skenario tertentu, tetapi mungkin menawarkan beberapa solusi alternatif yang tidak tergantung pada skenario atau lebih sesuai untuk yang lain. skenario.

Dengan jelas menyebut "sleep ()" berguna dalam banyak situasi ketika tenggat waktu ditentukan dengan jelas dalam istilah waktu dunia nyata, namun, ada sistem yang lebih canggih untuk menunggu dan memberi sinyal utas yang harus dipertimbangkan dan dipahami sebelum Anda mulai tidur ( ) ke dalam kode Anda, dan memasukkan pernyataan sleep () yang tidak perlu dalam kode Anda umumnya dianggap sebagai taktik pemula.

Jason Nelson
sumber
5

Ini adalah lingkaran 1). Berputar dan 2) .polling dari contoh Anda yang harus diperhatikan orang , bukan bagian Thread.Sleep (). Saya pikir Thread.Sleep () biasanya ditambahkan untuk dengan mudah meningkatkan kode yang berputar atau dalam loop polling, jadi ini hanya terkait dengan kode "buruk".

Selain itu, orang melakukan hal-hal seperti:

while(inWait)Thread.Sleep(5000); 

di mana variabel inWait tidak diakses dengan cara yang aman untuk thread, yang juga menyebabkan masalah.

Yang ingin dilihat pemrogram adalah utas yang dikontrol oleh konstruksi Peristiwa dan Pemberian Sinyal dan Penguncian, dan ketika Anda melakukannya, Anda tidak perlu Thread.Sleep (), dan kekhawatiran tentang akses variabel aman-thread juga dihilangkan. Sebagai contoh, dapatkah Anda membuat penangan peristiwa yang terkait dengan kelas FileSystemWatcher dan menggunakan peristiwa untuk memicu contoh ke-2 alih-alih perulangan?

Seperti yang disebutkan Andreas N., membaca Threading dalam C #, oleh Joe Albahari , benar-benar bagus.

mike
sumber
Jadi apa alternatif untuk melakukan apa yang ada di contoh kode Anda?
shinzou
kuhaku - Ya, pertanyaan bagus. Jelas Thread.Sleep () adalah pintasan, dan terkadang pintasan besar. Untuk contoh di atas, sisa kode dalam fungsi di bawah loop polling "while (inWait) Thread.Sleep (5000);" adalah fungsi baru. Fungsi baru tersebut adalah sebuah delegasi (fungsi callback) dan Anda meneruskannya ke apa pun yang menyetel flag "inWait", dan alih-alih mengubah flag "inWait", callback akan dipanggil. Ini adalah contoh terpendek yang dapat saya temukan: myelin.co.nz/notes/callbacks/cs-delegates.html
mike
Hanya untuk memastikan saya mendapatkannya, Anda bermaksud membungkus Thread.Sleep()dengan fungsi lain, dan memanggilnya di loop sementara?
shinzou
5

Sleep digunakan dalam kasus di mana program independen yang tidak dapat Anda kendalikan terkadang menggunakan resource yang umum digunakan (misalnya, file), yang perlu diakses oleh program Anda saat berjalan, dan saat resource digunakan oleh program tersebut. program lain program Anda diblokir dari menggunakannya. Dalam kasus ini, di mana Anda mengakses sumber daya dalam kode Anda, Anda meletakkan akses sumber daya dalam coba-tangkap (untuk menangkap pengecualian ketika Anda tidak dapat mengakses sumber daya), dan Anda meletakkannya di loop sementara. Jika sumber daya gratis, sleep tidak akan pernah dipanggil. Namun jika sumber daya diblokir, Anda akan tidur selama jangka waktu yang sesuai, dan mencoba mengakses sumber daya lagi (inilah alasan Anda melakukan perulangan). Namun, perlu diingat bahwa Anda harus meletakkan semacam pembatas pada loop, jadi ini bukan loop yang berpotensi tak terbatas.

Steve Greene
sumber
3

Saya memiliki kasus penggunaan yang tidak terlalu saya lihat di sini, dan akan berpendapat bahwa ini adalah alasan yang valid untuk menggunakan Thread.Sleep ():

Dalam aplikasi konsol yang menjalankan pekerjaan pembersihan, saya perlu melakukan sejumlah besar panggilan database yang cukup mahal, ke DB yang dibagikan oleh ribuan pengguna bersamaan. Agar tidak memalu DB dan mengecualikan orang lain selama berjam-jam, saya perlu jeda antar panggilan, dalam urutan 100 ms. Ini tidak terkait dengan waktu, hanya untuk memberikan akses ke DB untuk utas lainnya.

Menghabiskan 2000-8000 siklus pada peralihan konteks antar panggilan yang mungkin memerlukan 500 md untuk dieksekusi tidaklah berbahaya, seperti halnya memiliki 1 MB tumpukan untuk utas, yang berjalan sebagai satu contoh di server.

Henrik R Clausen
sumber
Ya, menggunakan Thread.Sleepaplikasi Konsol satu utas dan tujuan tunggal seperti yang Anda jelaskan tidak masalah.
Theodor Zoulias
-7

Saya setuju dengan banyak hal di sini, tetapi saya juga berpikir itu tergantung.

Baru-baru ini saya melakukan kode ini:

private void animate(FlowLayoutPanel element, int start, int end)
{
    bool asc = end > start;
    element.Show();
    while (start != end) {
        start += asc ? 1 : -1;
        element.Height = start;
        Thread.Sleep(1);
    }
    if (!asc)
    {
        element.Hide();
    }
    element.Focus();
}

Itu adalah fungsi animasi sederhana, dan saya menggunakannya Thread.Sleep.

Kesimpulan saya, jika itu berhasil, gunakanlah.


sumber
-8

Bagi Anda yang belum melihat satu pun argumen valid yang menentang penggunaan Thread.Sleep di SKENARIO 2, sebenarnya ada satu - keluar aplikasi ditahan oleh while loop (SKENARIO 1/3 hanya bodoh jadi tidak layak untuk lebih menyebutkan)

Banyak yang berpura-pura menjadi tahu, berteriak Thread.Sleep is evil gagal menyebutkan satu alasan yang sah bagi kita yang menuntut alasan praktis untuk tidak menggunakannya - tapi ini dia, terima kasih kepada Pete - Thread.Sleep itu Jahat (dapat dengan mudah dihindari dengan timer / handler)

    static void Main(string[] args)
    {
        Thread t = new Thread(new ThreadStart(ThreadFunc));
        t.Start();

        Console.WriteLine("Hit any key to exit.");
        Console.ReadLine();

        Console.WriteLine("App exiting");
        return;
    }

    static void ThreadFunc()
    {
        int i=0;
        try
        {
            while (true)
            {
                Console.WriteLine(Thread.CurrentThread.ThreadState.ToString() + " " + i);

                Thread.Sleep(1000 * 10);
                i++;
            }
        }
        finally
        {
            Console.WriteLine("Exiting while loop");
        }
        return;
    }
Swab Jat
sumber
9
-, tidak, Thread.Sleepbukan alasan untuk ini (utas baru merupakan loop sementara yang berkelanjutan)! Anda dapat menghapus Thread.Sleep-line - et voila: program tidak akan keluar juga ...
Andreas Niedermair