Thread.Sleep neden bu kadar zararlı


128

Sık sık Thread.Sleep();kullanılmaması gerektiğinden bahsedildiğini görüyorum , ancak bunun neden böyle olduğunu anlayamıyorum. Soruna Thread.Sleep();neden olabilecekse, aynı sonuca sahip güvenli olabilecek alternatif çözümler var mı?

Örneğin.

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

bir diğeri:

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);
    }
}

3
Blogun özeti 'Thread.sleep ()' yi kötüye kullanma 'olabilir.
Martin James

5
Zararlı olduğunu söyleyemem. Bunun gibi olduğunu söylemeyi tercih ederim, goto:yani sorunlarınız için muhtemelen daha iyi bir çözüm vardır Sleep.
Varsayılan

9
Bu goto, bir tasarım kokusundan çok bir kod kokusu gibi olan ile tam olarak aynı değildir . Bir derleyicinin gotokodunuza s eklemesinde yanlış bir şey yoktur : bilgisayarın kafası karışmaz. Ama Thread.Sleeptam olarak aynı değil; derleyiciler bu çağrıyı eklemez ve başka olumsuz sonuçları vardır. Ama evet, neredeyse her zaman daha iyi bir çözüm olduğu için onu kullanmanın yanlış olduğu genel düşüncesi kesinlikle doğrudur.
Cody Grey

32
Herkes yukarıdaki örneklerin neden kötü olduğuna dair fikir veriyor, ancak hiç kimse, verilen örneklerin amacını hala gerçekleştiren Thread.Sleep () kullanmayan yeniden yazılmış bir sürüm sunmadı.
StingyJack

3
Her sleep()kod girdiğinizde (veya test ettiğinizde ) bir köpek yavrusu ölüyor
Reza S

Yanıtlar:


163

Aramayla Thread.Sleepilgili sorunlar burada oldukça kısaca açıklanmıştır :

Thread.Sleepkullanımı vardır: bir MTA iş parçacığı üzerinde test / hata ayıklama sırasında uzun işlemleri simüle etmek. .NET'te onu kullanmak için başka bir neden yoktur.

Thread.Sleep(n), geçerli iş parçacığını en azından n milisaniyeler içinde oluşabilen zaman dilimleri (veya iş parçacığı kuantumları) sayısı kadar bloke etmek anlamına gelir . Bir zaman diliminin uzunluğu, farklı Windows sürümlerinde / türlerinde ve farklı işlemcilerde farklıdır ve genellikle 15 ila 30 milisaniye arasında değişir. Bu, iş parçacığının nmilisaniyeden daha uzun bir süre bloke edeceği anlamına gelir . İş parçacığınızın tam olarak nmilisaniyeler sonra yeniden uyanma olasılığı, imkansız olabildiğince imkansızdır. Yani, Thread.Sleepzamanlama için anlamsız .

İplikler sınırlı bir kaynaktır, oluşturmak için yaklaşık 200.000 döngü ve yok etmek için yaklaşık 100.000 döngü gerekir. Varsayılan olarak, yığını için 1 megabayt sanal bellek ayırırlar ve her bağlam anahtarı için 2.000-8.000 döngü kullanırlar. Bu, bekleyen herhangi bir ipliği büyük bir israf yapar.

Tercih edilen çözüm: WaitHandles

En çok yapılan hata Thread.Sleepbir while-construct ile kullanmaktır ( demo ve cevap , güzel blog girişi )

DÜZENLEME:
Cevabımı geliştirmek istiyorum:

2 farklı kullanım durumumuz var:

  1. Biz (kullanım devam etmelidir zaman belirli bir zaman aralığını biliyoruz çünkü bekliyoruz Thread.Sleep, System.Threading.Timerya da benzerlerini)

  2. Bekliyoruz çünkü bazı koşullar bir süre değişir ... anahtar kelime (ler) biraz zamandır ! koşul denetimi kod etki alanımızda ise, WaitHandles kullanmalıyız - aksi takdirde harici bileşen bir tür kanca sağlamalıdır ... Aksi takdirde tasarımı kötüdür!

Cevabım esas olarak 2. kullanım durumunu kapsıyor


30
Bugünün donanımını düşünürsek 1 MB belleğin büyük bir israf olduğunu düşünmem
Varsayılan

14
@Default hey, bunu orijinal yazarla tartışın :) ve bu her zaman kodunuza bağlıdır - veya daha iyisi: faktör ... ve ana konu "Konular sınırlı bir kaynaktır" - günümüz çocukları pek bir şey bilmiyor bazı uygulamaların verimliliği ve maliyeti hakkında, çünkü "donanım ucuzdur" ... ama bazen çok iyi kodlamanız gerekir
Andreas Niedermair

11
'Bu, bekleyen herhangi bir ipliği büyük bir israf yapar' değil mi? Bazı protokol özellikleri devam etmeden önce bir saniyelik bir duraklama talep ederse, 1 saniye ne bekleyecek? Bir yerlerde bir iplik beklemek zorunda kalacak! İş parçacığı oluşturma / yok etme ek yükü genellikle önemsizdir çünkü bir iş parçacığı başka nedenlerle zaten yükseltilmelidir ve işlemin ömrü boyunca çalışır. Spesifikasyondan biri 'pompayı açtıktan sonra, besleme valfini açmadan önce basıncın dengelenmesi için en az on saniye bekleyin' dediğinde bağlam değiştirmeden kaçınmanın herhangi bir yolunu görmek isterim.
Martin James

9
@CodyGray - Yazıyı tekrar okudum. Yorumlarımda hiçbir renkte balık görmüyorum. Andreas web'den çıktı: 'Thread.Sleep kullanılıyor: bir MTA iş parçacığı üzerinde test / hata ayıklama yaparken uzun işlemleri simüle etmek. .NET'te onu kullanmak için başka bir neden yok '. Bir sleep () çağrısının tam olarak gerekli olan şey olduğu birçok uygulama olduğunu iddia ediyorum. Geliştiricilerin leigonları (çünkü pek çoğu var), durum izleyicileri olarak olaylar / koşullar / semalar / her neyse, uyku () döngülerini kullanmakta ısrar ederse, bu, 'onu kullanmak için başka bir neden yok' iddiasının gerekçesi değildir. '.
Martin James

8
30 yıllık multiThreaded uygulama geliştirme sürecinde (çoğunlukla C ++ / Delphi / Windows), herhangi bir teslim edilebilir kodda uyku (0) veya uyku (1) döngülerine hiç ihtiyaç görmedim. Ara sıra, hata ayıklama amacıyla bu tür bir kodu soktum, ancak asla müşteriye ulaşmadı. 'her iş parçacığını tamamen kontrol etmeyen bir şey yazıyorsanız' - iş parçacıklarının mikro yönetimi, geliştirme personelinin mikro yönetimi kadar büyük bir hatadır. İş parçacığı yönetimi, işletim sisteminin orada olduğu şeydir - sağladığı araçlar kullanılmalıdır.
Martin James

34

SENARYO 1 - eşzamansız görevin tamamlanmasını bekleyin: WaitHandle / Auto | ManualResetEvent'in, bir iş parçacığının başka bir iş parçacığındaki görevin tamamlanmasını beklediği senaryoda kullanılması gerektiğini kabul ediyorum.

SENARYO 2 - döngü sırasında zamanlama: Bununla birlikte, kaba bir zamanlama mekanizması olarak (+ Thread.Sleep), bloke edilen Thread'ın tam olarak ne zaman "uyanacağını * bilmeyi gerektirmeyen uygulamaların% 99'u için mükemmel derecede iyidir . İş parçacığı oluşturmak için 200k döngü de geçersiz - zamanlama döngüsü iş parçacığı yine de oluşturulmalıdır ve 200k döngü sadece başka bir büyük sayıdır (bir dosya / soket / db çağrısı açmak için kaç döngü olduğunu söyleyin?).

Öyleyse + Thread.Sleep çalışıyorsa, neden işleri karmaşıklaştıralım? Yalnızca sözdizimi avukatları pratik olabilir !


Neyse ki, bir sonucu verimli bir şekilde beklemek için artık TaskCompletionSource'a sahibiz.
Austin Salgat

14

Bu soruyu, herhangi bir kimseye yardımcı olabilecek veya olmayabilecek bir kodlama-siyaset perspektifinden cevaplamak istiyorum. Ancak özellikle 9-5 kurumsal programcıya yönelik araçlarla uğraşırken, dokümantasyon yazan kişiler "olmamalı" ve "asla" gibi sözcükler kullanma eğiliminde olup "ne yaptığınızı gerçekten bilmediğiniz sürece bunu yapma Yapıyoruz ve neden ".

C # dünyasındaki diğer favorilerimden birkaçı, size "kilidi asla çağırmayın (bu)" veya "GC.Collect () 'i asla çağırmayın" demeleridir. Bu ikisi birçok blogda ve resmi belgede zorla ilan edilmiş ve IMO tamamen yanlış bilgilendirilmiştir. Bir düzeyde bu yanlış bilgi, yeni başlayanları alternatifleri tam olarak araştırmadan önce anlamadıkları şeyleri yapmaktan uzak tutması ve aynı zamanda arama motorları aracılığıyla GERÇEK bilgileri bulmayı zorlaştırmasıyla amacına hizmet ediyor. "neden olmasın?" sorusuna cevap vermezken size bir şey yapmamanızı söyleyen makalelere işaret ediyor gibi görünüyor.

Politik olarak, insanların "iyi tasarım" veya "kötü tasarım" olarak gördükleri şeye indirgeniyor. Resmi belgeler, başvurumun tasarımını dikte etmemelidir. Sleep () 'i çağırmamanız için gerçekten teknik bir neden varsa, o zaman IMO, onu belirli senaryolar altında çağırmanın tamamen uygun olduğunu belirtmelidir, ancak senaryodan bağımsız veya diğerine daha uygun alternatif çözümler sunabilir. senaryoları.

Açık bir şekilde "sleep ()" olarak adlandırmak, son tarihlerin gerçek zamanlı terimlerle açıkça tanımlandığı birçok durumda yararlıdır, ancak, siz uykuya dalmaya başlamadan önce dikkate alınması ve anlaşılması gereken iş parçacıkları beklemek ve sinyal vermek için daha karmaşık sistemler vardır ( ) kodunuza girmeniz ve kodunuza gereksiz uyku () ifadeleri atmanız genellikle yeni başlayanlar için bir taktik olarak kabul edilir.


5

İnsanların dikkat ettiği, Thread.Sleep () kısmı değil, örneklerinizin 1). Spinning ve 2) .polling döngüsüdür . Bence Thread.Sleep () genellikle dönen veya bir yoklama döngüsündeki kodu kolayca geliştirmek için eklenir, bu yüzden sadece "kötü" kodla ilişkilendirilir.

Ek olarak, insanlar aşağıdaki gibi şeyler yapar:

while(inWait)Thread.Sleep(5000); 

burada inWait değişkenine iş parçacığı açısından güvenli bir şekilde erişilmez ve bu da sorunlara neden olur.

Programcıların görmek istediği şey, Olaylar ve Sinyalleme ve Kilitleme yapıları tarafından kontrol edilen iş parçacıklarıdır ve bunu yaptığınızda, Thread.Sleep () 'ye ihtiyacınız olmayacak ve iş parçacığı güvenli değişken erişimi ile ilgili endişeler de ortadan kalkacaktır. Örnek olarak, FileSystemWatcher sınıfıyla ilişkili bir olay işleyicisi oluşturabilir ve döngü yerine 2. örneğinizi tetiklemek için bir olay kullanabilir misiniz?

Andreas N.'nin bahsettiği gibi, Joe Albahari'nin C # dilinde Threading'i oku , gerçekten çok iyi.


Öyleyse, kod örneğinizde ne yapmanın alternatifi nedir?
shinzou

kuhaku - Evet, güzel soru. Açıkçası Thread.Sleep () bir kısayol ve bazen büyük bir kısayol. Yukarıdaki örnek için, yoklama döngüsünün altındaki işlevdeki kodun geri kalanı "while (inWait) Thread.Sleep (5000);" yeni bir işlevdir. Bu yeni işlev bir temsilcidir (geri arama işlevi) ve bunu "inWait" bayrağını ayarlayan her şeye iletirsiniz ve "inWait" bayrağını değiştirmek yerine geri arama çağrılır. Bu bulabildiğim en kısa örnekti: myelin.co.nz/notes/callbacks/cs-delegates.html
mike

Sadece anladığımdan emin olmak için, Thread.Sleep()başka bir işlevle sarmalamak ve bunu while döngüsünde mi çağırmak istiyorsunuz?
shinzou

5

Uyku, üzerinde kontrolünüz olmayan bağımsız programların bazen yaygın olarak kullanılan bir kaynağı (örneğin bir dosya) kullanabildiği, programınızın çalıştığında erişmesi gereken ve kaynak bunlar tarafından kullanımda olduğu durumlarda kullanılır. programınızın onu kullanmasının engellendiği diğer programlar. Bu durumda, kodunuzdaki kaynağa eriştiğinizde, kaynağa erişiminizi bir dene-yakalamaya koyarsınız (kaynağa erişemediğinizde istisnayı yakalamak için) ve bunu bir süre döngüsüne koyarsınız. Kaynak bedava ise uyku asla çağrılmaz. Ancak kaynak engellenmişse, uygun bir süre uyur ve kaynağa yeniden erişmeye çalışırsınız (bu yüzden döngü yapıyorsunuz). Ancak, döngüye bir tür sınırlayıcı koymanız gerektiğini unutmayın, bu nedenle bu potansiyel olarak sonsuz bir döngü değildir.


3

Burada ele alındığını pek görmediğim bir kullanım durumum var ve bunun Thread.Sleep () kullanmak için geçerli bir neden olduğunu savunacağım:

Temizleme işleri çalıştıran bir konsol uygulamasında, binlerce eşzamanlı kullanıcı tarafından paylaşılan bir DB'ye büyük miktarda oldukça pahalı veritabanı çağrısı yapmam gerekiyor. DB'yi çekiçlememek ve diğerlerini saatlerce dışlamak için, aramalar arasında 100 ms'lik bir duraklama ihtiyacım olacak. Bu, zamanlama ile ilgili değildir, sadece diğer iş parçacıkları için DB'ye erişim sağlamakla ilgilidir.

Bir sunucuda tek bir örnek olarak çalışan iş parçacığı için 1 MB yığın olması gibi, yürütmesi 500 ms sürebilen çağrılar arasında bağlam geçişi için 2000-8000 döngü harcamak zararsızdır.


Yeap, Thread.Sleeptanımladığınız gibi tek iş parçacıklı, tek amaçlı bir Konsol uygulamasını kullanmak tamamen sorun değil.
Theodor Zoulias

-7

Buradaki birçok kişiye katılıyorum, ancak bunun da değiştiğini düşünüyorum.

Son zamanlarda bu kodu yaptım:

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();
}

Bu basit bir animasyon işleviydi ve Thread.Sleepüzerinde kullandım .

Benim sonucum, eğer işi yaparsa, onu kullanın.


-8

SENARYO 2'de Thread.Sleep kullanımına karşı geçerli bir argüman görmemiş olanlar için, gerçekten bir tane var - uygulama çıkışı while döngüsü tarafından tutulacak (SENARYO 1/3 sadece aptaldır, bu yüzden daha fazlasına değmez ) söz

Thread.Sleep kötüdür diye haberdeymiş gibi davranan birçok kişi, onu kullanmamak için pratik bir sebep talep eden bizler için geçerli tek bir nedenden bahsetmekte başarısız oldu - ama işte burada, Pete sayesinde - Thread. Kötü (bir zamanlayıcı / işleyici ile kolayca önlenebilir)

    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;
    }

9
-, hayır, Thread.Sleepbunun nedeni değil (yeni iş parçacığı ve sürekli bir süre döngüsü)! Thread.Sleep-line'ı kaldırabilirsiniz - ve işte: program da çıkmayacak ...
Andreas Niedermair
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.