Почему Thread.Sleep так вреден

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);
    }
}
Burimi
источник
3
Краткое содержание блога может быть таким: «Не злоупотребляйте Thread.sleep ()».
Мартин Джеймс
5
Я бы не сказал, что это вредно. Я бы сказал, что это похоже на goto:то, что, вероятно, есть лучшее решение ваших проблем, чем Sleep.
По умолчанию,
9
Это не совсем то же самое goto, что больше похоже на запах кода, чем на запах дизайна. Нет ничего плохого в том, что компилятор вставляет gotos в ваш код: компьютер не запутается. Но Thread.Sleepне совсем то же самое; компиляторы не вставляют этот вызов, и это имеет другие негативные последствия. Но да, общее мнение о том, что его использовать неправильно, потому что почти всегда есть лучшее решение, безусловно, верно.
Коди Грей
32
Все высказывают свое мнение о том, почему приведенные выше примеры плохи, но никто не представил переписанную версию, в которой не используется Thread.Sleep (), которая по-прежнему выполняет цель данных примеров.
StingyJack 06
3
Каждый раз, когда вы вводите sleep()код (или тесты), умирает щенок
Реза S

Ответы:

163

Проблемы , связанные с вызовом Thread.SleepARE объясняется довольно лаконично здесь :

Thread.Sleepимеет свое применение: имитация длительных операций при тестировании / отладке в потоке MTA. В .NET нет другой причины использовать его.

Thread.Sleep(n)означает блокировать текущий поток, по крайней мере, на количество временных интервалов (или квантов потока), которые могут произойти в течение n миллисекунд. Длина кванта времени различается для разных версий / типов Windows и разных процессоров и обычно составляет от 15 до 30 миллисекунд. Это означает, что поток почти гарантированно блокируется более чем на nмиллисекунды. Вероятность того, что ваш поток снова проснется ровно через nмиллисекунды, практически невозможна. Так Thread.Sleepчто рассчитывать время бессмысленно .

Потоки - это ограниченный ресурс, для их создания требуется около 200 000 циклов, а для уничтожения - около 100 000 циклов. По умолчанию они резервируют 1 мегабайт виртуальной памяти для своего стека и используют 2 000-8 000 циклов для каждого переключения контекста. Это превращает любой ожидающий поток в огромную трату.

Предпочтительное решение: WaitHandles

Самая частая ошибка - это использование Thread.Sleepконструкции while ( демонстрация и ответ , хорошая запись в блоге )

РЕДАКТИРОВАТЬ:
Я хотел бы улучшить свой ответ:

У нас есть 2 разных варианта использования:

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

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

Мой ответ в основном касается варианта использования 2

Андреас Нидермайр
источник
30
Я бы не назвал 1 МБ памяти огромной тратой, учитывая сегодняшнее оборудование
По умолчанию
14
@ По умолчанию, обсудите это с первоначальным автором :) и это всегда зависит от вашего кода - или лучше: фактор ... и главная проблема - "Потоки - ограниченный ресурс" - сегодняшние дети мало что знают об эффективности и стоимости определенных реализаций, потому что «оборудование дешево» ... но иногда вам нужно очень сильно кодировать
Андреас Нидермайр
11
«Это превращает любую ожидающую нить в огромную трату», а? Если какая-то спецификация протокола требует паузы в одну секунду перед продолжением, что будет ждать 1 секунду? Какой-то поток где-то придется подождать! Накладные расходы на создание / уничтожение потока часто не имеют значения, потому что поток все равно должен быть вызван по другим причинам, и он выполняется в течение всего времени существования процесса. Мне было бы интересно увидеть какой-либо способ избежать переключения контекста, когда в спецификации говорится: «после включения насоса подождите не менее десяти секунд, пока давление не стабилизируется, прежде чем открывать подающий клапан».
Мартин Джеймс
9
@CodyGray - Я снова прочитал пост. В своих комментариях я не вижу рыбок какого-либо цвета. Андреас вытащил из Интернета: «Thread.Sleep имеет свое применение: имитирует длительные операции во время тестирования / отладки в потоке MTA. В .NET нет другой причины использовать его ». Я утверждаю, что существует множество приложений, в которых вызов sleep () - это именно то, что требуется. Если многие разработчики (а их много) настаивают на использовании циклов sleep () в качестве мониторов условий, которые следует заменить на events / condvars / semas / что угодно, это не является оправданием для утверждения, что «нет другой причины использовать его. ».
Мартин Джеймс
8
За 30 лет разработки многопоточных приложений (в основном C ++ / Delphi / Windows) я никогда не видел необходимости в циклах сна (0) или сна (1) в каком-либо доставляемом коде. Иногда я вставляю такой код в целях отладки, но он так и не попал к заказчику. «если вы пишете что-то, что не полностью контролирует каждый поток» - микроменеджмент потоков - такая же большая ошибка, как и микроменеджмент сотрудников разработчиков. Управление потоками - это то, для чего существует ОС - следует использовать инструменты, которые она предоставляет.
Мартин Джеймс
34

СЦЕНАРИЙ 1 - дождитесь завершения асинхронной задачи: я согласен с тем, что WaitHandle / Auto | ManualResetEvent следует использовать в сценарии, когда поток ожидает завершения задачи в другом потоке.

СЦЕНАРИЙ 2 - синхронизация цикла while: однако, как грубый механизм синхронизации (while + Thread.Sleep) отлично подходит для 99% приложений, которые НЕ требуют точного знания , когда заблокированный поток должен «проснуться» *. Аргумент, который он принимает 200 тыс. Циклов для создания потока также недопустимы - поток цикла синхронизации необходимо создать в любом случае, а 200 тыс. Циклов - это просто еще одно большое число (скажите мне, сколько циклов для открытия вызовов файла / сокета / базы данных?).

Итак, если while + Thread.Sleep работает, зачем все усложнять? Только юристы по синтаксису будут практичными !

Swab.Jat
источник
К счастью, теперь у нас есть TaskCompletionSource для эффективного ожидания результата.
Остин Салгат,
14

Я хотел бы ответить на этот вопрос с точки зрения политики кодирования, которая может быть полезна, а может и нет. Но особенно когда вы имеете дело с инструментами, предназначенными для 9-5 корпоративных программистов, люди, которые пишут документацию, склонны использовать такие слова, как «не следует» и «никогда», чтобы означать «не делайте этого, если вы действительно не знаете, что вы делаем и зачем ».

Еще парочка моих любимых в мире C # - это то, что они говорят вам «никогда не вызывать lock (this)» или «никогда не вызывать GC.Collect ()». Эти два случая настоятельно заявлены во многих блогах и официальной документации, а ИМО являются полной дезинформацией. На каком-то уровне эта дезинформация служит своей цели, поскольку она удерживает новичков от занятий, которые они не понимают, до полного исследования альтернатив, но в то же время затрудняет поиск РЕАЛЬНОЙ информации через поисковые системы, которые все Кажется, что они указывают на статьи, в которых говорится не делать чего-либо, но не дают ответа на вопрос «почему бы и нет?».

С политической точки зрения, все сводится к тому, что люди считают «хорошим дизайном» или «плохим дизайном». Официальная документация не должна диктовать дизайн моего приложения. Если есть действительно техническая причина, по которой вы не должны вызывать sleep (), то в документации IMO должно быть указано, что это нормально называть его в определенных сценариях, но, возможно, предлагать некоторые альтернативные решения, которые не зависят от сценария или более подходят для другого сценарии.

Ясно, что вызов "sleep ()" полезен во многих ситуациях, когда крайние сроки четко определены в терминах реального времени, однако существуют более сложные системы для ожидания и сигнализации потоков, которые следует рассмотреть и понять, прежде чем вы начнете бросать спать ( ) в свой код, а добавление ненужных операторов sleep () в код обычно считается тактикой новичков.

Джейсон Нельсон
источник
5

Это цикл 1) .spinning и 2) .polling ваших примеров, против которого люди предостерегают , а не часть Thread.Sleep (). Я думаю, что Thread.Sleep () обычно добавляется, чтобы легко улучшить код, который вращается или находится в цикле опроса, поэтому он просто связан с «плохим» кодом.

Вдобавок люди делают такие вещи, как:

while(inWait)Thread.Sleep(5000); 

где переменная inWait не доступна поточно-ориентированным способом, что также вызывает проблемы.

Программисты хотят видеть потоки, управляемые конструкциями Events, Signaling и Locking, и когда вы это сделаете, у вас не будет необходимости в Thread.Sleep (), а также исчезнут опасения по поводу безопасного для потоков доступа к переменным. Например, не могли бы вы создать обработчик событий, связанный с классом FileSystemWatcher, и использовать событие для запуска второго примера вместо цикла?

Как упомянул Андреас Н., прочтите « Потоки в C #» Джо Альбахари , это действительно действительно хорошо.

Майк
источник
Итак, какова альтернатива тому, что указано в вашем примере кода?
shinzou
кухаку - Да, хороший вопрос. Очевидно, Thread.Sleep () - это ярлык, а иногда и большой ярлык. В приведенном выше примере остальная часть кода в функции ниже цикла опроса «while (inWait) Thread.Sleep (5000);» является новой функцией. Эта новая функция является делегатом (функцией обратного вызова), и вы передаете ее тому, что устанавливает флаг «inWait», и вместо изменения флага «inWait» вызывается обратный вызов. Это был самый короткий пример, который я смог найти: myelin.co.nz/notes/callbacks/cs-delegates.html
mike
Просто чтобы убедиться, что я понял, вы хотите обернуть Thread.Sleep()другую функцию и вызвать ее в цикле while?
shinzou
5

Спящий режим используется в тех случаях, когда независимые программы, которые вы не контролируете, могут иногда использовать обычно используемый ресурс (например, файл), к которому вашей программе требуется доступ при запуске, и когда ресурс используется этими другие программы ваша программа заблокирована от ее использования. В этом случае, когда вы обращаетесь к ресурсу в своем коде, вы помещаете свой доступ к ресурсу в try-catch (чтобы поймать исключение, когда вы не можете получить доступ к ресурсу), и вы помещаете это в цикл while. Если ресурс свободен, сон никогда не вызывается. Но если ресурс заблокирован, вы спите в течение определенного времени и снова пытаетесь получить доступ к ресурсу (вот почему вы зацикливаетесь). Однако имейте в виду, что вы должны установить какой-то ограничитель на цикл, чтобы он не был потенциально бесконечным.

Стив Грин
источник
3

У меня есть вариант использования, который я не совсем вижу здесь, и я буду утверждать, что это веская причина использовать Thread.Sleep ():

В консольном приложении, выполняющем задания очистки, мне нужно сделать большое количество довольно дорогостоящих вызовов базы данных к базе данных, совместно используемой тысячами одновременно работающих пользователей. Чтобы не забивать БД и часами исключать других, мне понадобится пауза между вызовами, порядка 100 мс. Это не связано с таймингом, а просто с предоставлением доступа к БД для других потоков.

Тратить 2000–8000 циклов на переключение контекста между вызовами, выполнение которых может занять 500 мс, не представляет опасности, как и наличие 1 МБ стека для потока, который выполняется как единственный экземпляр на сервере.

Хенрик Р. Клаузен
источник
Да, использование 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 в СЦЕНАРИИ 2, действительно есть один - выход из приложения задерживается циклом while (СЦЕНАРИЙ 1/3 просто глуп, поэтому не заслуживает большего. упоминая)

Многие, кто притворяются знающими, кричащими Thread.Sleep is evil, не упомянули единственную вескую причину для тех из нас, кто требовал практической причины не использовать его - но вот оно, спасибо Питу - 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;
    }
Swab.Jat
источник
9
-, нет, Thread.Sleepэто не причина (новый поток и непрерывный цикл while)! вы можете просто удалить Thread.Sleepстроку - и вуаля: программа тоже не выйдет ...
Андреас Нидермайр,