Чому Thread.Sep так шкідливий


128

Я часто бачу, як згадується, що Thread.Sleep();його не слід використовувати, але я не можу зрозуміти, чому це так. Якщо це Thread.Sleep();може спричинити неприємності, чи є альтернативні рішення з таким же результатом, які були б безпечними?

напр.

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

ще один:

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
Підсумок блогу може бути "не зловживайте Thread.sleep ()".
Мартін Джеймс

5
Я б не сказав, що це шкідливо. Я б сказала, що це як, goto:тобто, мабуть, краще рішення для ваших проблем, ніж Sleep.
За замовчуванням

9
Це не зовсім те саме goto, що більше нагадує кодовий запах, ніж дизайнерський запах. Немає нічого поганого в тому, що компілятор вставляє gotos у ваш код: комп'ютер не плутається. Але Thread.Sleepне зовсім те саме; компілятори не вставляють цей виклик, і це має інші негативні наслідки. Але так, загальна думка про те, що використовувати його неправильно, оскільки майже завжди краще рішення, безумовно, правильне.
Коді Грей

32
Усі дають думку про те, чому вищевказані приклади погані, але ніхто не надав перезаписану версію, яка не використовує Thread.Sleep (), яка все ще виконує мету наведених прикладів.
StingyJack

3
Щоразу, коли ви вводите sleep()код (або тести), цуценя помирає
Reza S

Відповіді:


163

Проблеми , пов'язані з викликом Thread.SleepARE пояснюється досить лаконічно тут :

Thread.Sleepмає своє використання: моделювання тривалих операцій під час тестування / налагодження на потоці MTA. У .NET немає інших причин використовувати його.

Thread.Sleep(n)означає блокувати поточний потік щонайменше за кількість часових шрифтів (або квантів потоку), які можуть виникнути протягом n мілісекунд. Довжина часового відрізка різна в різних версіях / типах Windows та різних процесорах і зазвичай становить від 15 до 30 мілісекунд. Це означає, що нитка майже гарантовано блокується більше, ніж nмілісекунд. Ймовірність того, що ваша нитка знову пробудеться через nмілісекунди, настільки ж неможлива, наскільки це неможливо. Отже, Thread.Sleepбезглуздо для термінів .

Нитки - це обмежений ресурс, для створення їх потрібно приблизно 200 000 циклів і знищити близько 100 000 циклів. За замовчуванням вони резервують 1 мегабайт віртуальної пам'яті для її стека і використовують 2000-8000 циклів для кожного контекстного комутатора. Це робить будь-яку нитку очікування величезною тратою.

Краще рішення: WaitHandles

Найбільш помилковою помилкою є використання Thread.Sleepконструкції while ( демонстрація та відповідь , приємний запис у блозі )

EDIT:
Я хотів би покращити свою відповідь:

У нас є 2 різних випадки використання:

  1. Ми чекаємо , тому що ми знаємо конкретний відрізок часу , коли ми повинні продовжувати (використовувати Thread.Sleep, System.Threading.Timerабо двійники)

  2. Ми чекаємо, оскільки деякий час змінюється якийсь час ... ключове слово (и) є / є деякий час ! якщо перевірка стану знаходиться в нашому кодовому домені, ми повинні використовувати WaitHandles - інакше зовнішній компонент повинен передбачати якісь гачки ... якщо це не його дизайн поганий!

Моя відповідь переважно стосується випадку 2


30
Я б не назвав 1 Мб пам'яті величезною тратою, враховуючи сьогоднішнє обладнання
Типово,

14
@ За замовчуванням ей, обговоріть це з оригінальним автором :), і це завжди залежить від вашого коду - а ще краще: фактор ... а головне питання - "Нитки - обмежений ресурс" - сьогоднішні діти не знають багато щодо ефективності та вартості певних реалізацій, оскільки "апаратне забезпечення дешеве" ... але іноді потрібно кодувати дуже optd
Andreas Niedermair

11
"Це робить будь-яку нитку очікування величезною тратою" так? Якщо якась специфікація протоколу вимагає одно секундної паузи, перш ніж продовжувати, що чекатиме 1 секунда? Якась нитка, десь, доведеться чекати! Накладні витрати на створення / знищення ниток часто не мають значення, тому що нитка повинна бути піднята в будь-якому випадку з інших причин, і вона працює протягом всього процесу. Мені було б цікаво побачити будь-який спосіб уникнути контекстних перемикань, коли специфікація каже: "після включення насоса зачекайте принаймні десять секунд, щоб тиск стабілізувався перед відкриттям подаючого клапана".
Мартін Джеймс

9
@CodyGray - я знову прочитав публікацію. Я не бачу риби будь-якого кольору в своїх коментарях. Андреас витяг з Інтернету: "Thread.Sleep - це використання: моделювання тривалих операцій під час тестування / налагодження на потоці MTA. У .NET немає інших причин використовувати його '. Я стверджую, що існує багато додатків, де дзвінок сну () - це просто те, що потрібно. Якщо лейгони розробників, (бо їх багато), наполягають на використанні циклів сну () як моніторів стану, які слід замінити подіями / помірниками / семами / що завгодно, це не є виправданням для твердження про те, що "немає інших причин використовувати його '.
Мартін Джеймс

8
За 30 років розвитку багатопотокової програми (в основному C ++ / Delphi / Windows) я жодного разу не бачив необхідності у сні (0) або режимі сну (1) в будь-якому доступному коді. Іноді я вдавав такий код у цілі налагодження, але він ніколи не пробивався до замовника. "якщо ви пишете щось, що не повністю контролює кожну нитку" - мікроуправління нитками є настільки ж великою помилкою, як мікроуправління персоналом розвитку. Управління нитками - це те, для чого потрібна ОС - використовуються інструменти, які вона надає.
Мартін Джеймс

34

SCENARIO 1 - чекайте завершення завдання асинхронізації: я погоджуюся, що WaitHandle / Auto | ManualResetEvent слід використовувати в сценарії, коли нитка чекає завершення завдання на іншому потоці.

SCENARIO 2 - синхронізація під час циклу: Однак, як сирий механізм синхронізації (у той час як + Thread.Sleep) ідеально добре для 99% програм, які НЕ вимагають точно знати , коли заблокована нитка повинна "прокинутися". Аргумент, який потрібно 200k циклів для створення потоку також недійсні - нитка циклу синхронізації повинна бути створена в будь-якому випадку, і 200k циклів - лише ще одне велике число (скажіть мені, скільки циклів відкрити файл / сокет / db дзвінки?).

Тож якщо хоч + Thread.Sleep працює, навіщо ускладнювати речі? Тільки синтаксичних адвокати, бути практичними !


На щастя, зараз у нас є TaskCompletionSource для ефективного очікування результату.
Остін Сальгат

14

Я хотів би відповісти на це запитання з точки зору політики кодування, яка може чи не може бути корисною для будь-кого. Але особливо коли ви маєте справу з інструментами, призначеними для корпоративних програмістів 9-5, люди, які пишуть документацію, як правило, використовують слова типу "не слід" і "ніколи" не означають "цього не робити, якщо ви дійсно не знаєте, що ви ти робиш і чому ".

Кілька моїх улюблених у світі C # - це те, що вони говорять вам "ніколи не дзвоніть блокувати (це)" або "ніколи не дзвоніть GC.Collect ()". Ці двоє насильно декларуються у багатьох блогах та офіційній документації, а ІМО - це повна дезінформація. На деякому рівні ця дезінформація служить своїй меті, оскільки вона не дозволяє новачкам робити те, чого вони не розуміють, перш ніж повністю вивчити альтернативи, але в той же час ускладнює пошук реальної інформації за допомогою пошукових систем, які всі здається, вказують на статті, які говорять вам про те, щоб не робити чогось, не пропонуючи відповіді на питання "чому б і ні?"

Політично це зводиться до того, що люди вважають "хорошим дизайном" або "поганим дизайном". Офіційна документація не повинна диктувати дизайн моєї заявки. Якщо є справді технічна причина, що ви не повинні викликати сон (), то в документації ІМО повинно бути зазначено, що цілком нормально називати його за певними сценаріями, але, можливо, пропонувати деякі альтернативні рішення, які не залежать від сценарію або підходять для іншого сценаріїв.

Чіткий виклик "сну ()" корисний у багатьох ситуаціях, коли строки чітко визначені в реальному часі, однак існують більш досконалі системи для очікування та сигналізації потоків, які слід враховувати та розуміти, перш ніж починати кидати сон ( ) у свій код, і кидання зайвих висловлювань сну () у ваш код, як правило, вважається тактикою для початківців.


5

Люди застерігають саме 1) .спінінг та 2). Цикл опитування ваших прикладів , а не частина Thread.Sleep (). Я думаю, що Thread.Sleep () зазвичай додається для легкого вдосконалення коду, який крутиться або знаходиться в циклі опитування, тому він просто асоціюється з "поганим" кодом.

Крім того, люди роблять такі речі, як:

while(inWait)Thread.Sleep(5000); 

де змінна inWait не має доступу до потоку, що також спричиняє проблеми.

Програмісти хочуть бачити - це потоки, керовані структурами "Події" та "Сигналізація та блокування", і коли ви цього не зробите, Thread.Sleep (), а також занепокоєння щодо безпечного потокового змінного доступу також усувається. Як приклад, чи можете ви створити обробник подій, пов’язаний з класом FileSystemWatcher, і використати подію для запуску 2-го прикладу замість циклу?

Як згадував Андреас Н., читайте " Threading in C #" Джо Альбахарі , це дійсно добре.


То яка альтернатива виконувати те, що є у прикладі коду?
shinzou

кухаку - Так, гарне запитання. Очевидно, що Thread.Sleep () - це ярлик, а іноді і великий ярлик. Для наведеного вище прикладу, решта коду у функції під циклом опитування "while (inWait) Thread.Sleep (5000);" - це нова функція. Ця нова функція є делегатом (функція зворотного виклику), і ви передаєте її будь-якому параметру встановлення прапора "inWait", і замість зміни прапора "inWait" викликається зворотний виклик. Це був найкоротший приклад, який я міг знайти: myelin.co.nz/notes/callbacks/cs-delegates.html
Майк

Просто, щоб переконатися, що я його отримав, ти маєш на увазі завершити роботу Thread.Sleep()з іншою функцією, і зателефонувати це в циклі "час"?
shinzou

5

Сон використовується в тих випадках, коли незалежна програма (програми), над якою ви не маєте контролю, іноді може використовувати загальновживаний ресурс (скажімо, файл), до якого вашій програмі потрібно отримати доступ, коли вона працює, і коли цей ресурс використовується цими інших програм вашій програмі заборонено використовувати її. У цьому випадку, коли ви отримуєте доступ до ресурсу у своєму коді, ви ставите свій доступ до ресурсу у спробі лову (щоб вилучити виняток, коли ви не можете отримати доступ до ресурсу), і ви ставите це в циклі часу. Якщо ресурс вільний, сон ніколи не викликається. Але якщо ресурс заблокований, ви спите протягом відповідної кількості часу та намагаєтесь знову отримати доступ до ресурсу (саме тому ви циклічно). Однак майте на увазі, що на петлю потрібно поставити якийсь обмежувач, тому це не потенційно нескінченна петля.


3

У мене є випадок використання, який я не дуже бачу тут, і я стверджую, що це поважна причина використання Thread.Sleep ():

У консольному додатку, що виконує завдання очищення, мені потрібно здійснити велику кількість досить дорогих дзвінків до бази даних, до БД, якими поділяються тисячі одночасних користувачів. Щоб не забивати БД і не виключати інших протягом годин, мені знадобиться пауза між дзвінками, порядку 100 мс. Це не пов’язано з тимчасовим часом, а лише з отриманням доступу до БД для інших потоків.

Витрата 2000-8000 циклів на перемикання контексту між дзвінками, на виконання яких може знадобитися 500 мс, є доброякісним, як і у 1 стека MB для потоку, який працює як один екземпляр на сервері.


Так, за допомогою однопотокового Thread.Sleepодноцільового консольного додатка, як описаного вами, цілком нормально.
Теодор Зуліяс

-7

Я згоден з багатьма тут, але також думаю, що це залежить.

Нещодавно я зробив цей код:

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

Це була проста анімаційна функція, і я її використовував Thread.Sleep .

Мій висновок, якщо це виконує роботу, використовуйте його.


-8

Для тих із вас, хто не бачив жодного вагомого аргументу проти використання Thread.Sleep в SCENARIO 2, дійсно є один - вихід програми триматиметься циклом while (SCENARIO 1/3 - просто дурний, тому не вартий більше згадуючи)

Багато хто, хто претендує на те, що він знає, кричить Тема. Сон - це зло, не вдалося згадати єдиної поважної причини для тих із нас, хто вимагав практичної причини не використовувати його - але ось це, завдяки Піту - Thread.Sleep є Злом (його можна легко уникнути за допомогою таймера / обробника)

    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
-, nope, Thread.Sleepце не причина для цього (нова нитка - це неперервний цикл while-loop)! ви можете просто видалити Thread.Sleep-line - et voila: програма також не вийде ...
Andreas Niedermair
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.