Существуют ли случаи, когда предпочтительнее использовать простой старый объект Thread вместо одной из новых конструкций?

Я вижу много людей в сообщениях в блогах и здесь, на SO, избегая или не рекомендуя использовать класс Thread в последних версиях С# (и я имею в виду, конечно, 4.0+, с добавлением Task и друзья). Еще до этого были дебаты о том, что во многих случаях обычная функциональность старого потока может быть заменена классом ThreadPool.

Кроме того, другие специализированные механизмы далее показывают класс Thread менее привлекательный, например Timer, заменяющий уродливую комбинацию Thread + Sleep, тогда как для GUI мы имеем BackgroundWorker и т.д.

Тем не менее, Thread, по-видимому, остается очень знакомой концепцией для некоторых людей (включая меня), людей, которые, столкнувшись с задачей, связанной с каким-то параллельным исполнением, переходят непосредственно к использованию старого старого Thread класс. В последнее время мне стало интересно, если настало время попрактиковаться.

Итак, мой вопрос: есть ли случаи, когда это необходимо или полезно использовать простой старый объект Thread вместо одной из вышеперечисленных конструкций?

Ответ 1

Класс Thread не может быть устаревшим, потому что, очевидно, это деталь реализации всех тех других шаблонов, которые вы упоминаете.

Но это не ваш вопрос; ваш вопрос

Есть ли случаи, когда необходимо или полезно использовать простой старый объект Thread вместо одной из вышеперечисленных конструкций?

Конечно. Именно в тех случаях, когда одна из конструкций более высокого уровня не отвечает вашим потребностям.

Мой совет: если вы окажетесь в ситуации, когда существующие инструменты с более высокой абстракцией не отвечают вашим потребностям, и вы хотите реализовать решение с помощью потоков, тогда вы должны определить недостающую абстракцию, которая вам действительно нужна, а затем реализуйте эту абстракцию с помощью потоков, а затем используйте абстракцию.

Ответ 2

Потоки являются основным строительным блоком для определенных вещей (а именно parallelism и асинхронностью), и поэтому их не следует отбирать. Тем не менее, для большинства людей и большинства случаев использования есть более подходящие вещи, которые вы упомянули, например, пулы потоков (которые обеспечивают хороший способ обработки многих небольших заданий параллельно без перегрузки машины путем нереста 2000 потоков одновременно), BackgroundWorker (который инкапсулирует полезные события для одной короткой работы).

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

В аналогичном ключе .NET не запрещает использование массивов, несмотря на то, что List<T> лучше подходит для многих случаев, когда люди используют массивы. Просто потому, что вы все еще можете создавать вещи, которые не покрываются стандартной библиотекой lib.

Ответ 3

Task и Thread - разные абстракции. Если вы хотите смоделировать поток, класс Thread по-прежнему является наиболее подходящим выбором. Например. если вам нужно взаимодействовать с текущим потоком, я не вижу для этого более подходящих типов.

Однако, как вы указываете,.NET добавил несколько выделенных абстракций, которые во многих случаях предпочтительнее Thread.

Ответ 4

Класс Thread не устарел, он по-прежнему полезен в особых обстоятельствах.

Где я работаю, мы написали "фоновый процессор" как часть системы управления контентом: служба Windows, которая контролирует каталоги, адреса электронной почты и RSS-каналы, и каждый раз, когда что-то новое появляется, выполняют на нем задачу - обычно для импорта данных.

Попытки использовать пул потоков для этого не помогли: он пытается одновременно загружать слишком много вещей и мусор дисков, поэтому мы внедрили нашу собственную систему опроса и выполнения, используя непосредственно класс Thread.

Ответ 5

Новые опции делают использование и использование (дорогих) потоков менее частыми.

которые, столкнувшись с задачей, которая предполагает какое-то параллельное выполнение, переходят непосредственно к использованию старого старого класса Thread.

Это очень дорогой и относительно сложный способ делать вещи параллельно.

Обратите внимание, что расход больше всего: вы не можете использовать полную нить, чтобы выполнить небольшую работу, это было бы контрпродуктивно. ThreadPool борется с затратами, класс задач - сложности (исключения, ожидания и отмены).

Ответ 6

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

Например, если вы пишете приложение, которое подписывается на получение сообщений из какой-то очереди сообщений, и приложение будет делать больше, чем просто обрабатывать эти сообщения, тогда было бы полезно использовать Thread, потому что нить будет самодостаточной (т.е. вы не дожидаетесь ее завершения), и она недолговечна. Использование класса ThreadPool больше подходит для организации очередей непродолжительных рабочих элементов и позволяет классу ThreadPool эффективно обрабатывать каждый из них, поскольку новый поток доступен. Задачи могут использоваться там, где вы будете использовать Thread напрямую, но в приведенном выше сценарии я не думаю, что они вас купят. Они помогают вам более легко взаимодействовать с потоком (что не соответствует приведенному выше сценарию), и они помогают определить, сколько потоков фактически должно использоваться для заданного набора задач на основе количества процессоров, которые у вас есть (что не так вы хотите, так что вы сказали бы, что ваша задача LongRunning, в этом случае в текущей реализации 4.0 она просто создаст отдельный не объединенный поток).

Ответ 7

Вероятно, не ответ, который вы ожидали, но я все время использую Thread при кодировании с .NET Micro Framework. MF довольно сокращен и не включает абстракции более высокого уровня, а класс Thread является сверхгибким, когда вам нужно получить последний бит производительности из процессора с низкой частотой.

Ответ 8

Вы можете сравнить класс Thread с ADO.NET. Это не рекомендованный инструмент для выполнения работы, но не устаревший. Другие инструменты строятся на нем, чтобы облегчить работу.

Неправильно использовать класс Thread над другими вещами, особенно если эти вещи не предоставляют необходимую вам функциональность.

Ответ 9

Это не определенно устарело.

Проблема с многопоточными приложениями заключается в том, что их очень сложно получить правильно (часто важны неопределенное поведение, ввод, вывод, а также внутреннее состояние), поэтому программист должен максимально эффективно работать с каркасом/инструментами. Отбросьте это. Но смертельный враг абстракции - это производительность.

Итак, мой вопрос: существуют ли случаи, когда это необходимо или полезно для использования простого старого объекта Thread вместо одного из вышеперечисленных строит?

Я бы пошел с Threads и locks только в случае серьезных проблем с производительностью и высокой производительности.

Ответ 10

Я всегда использовал класс Thread, когда мне нужно было следить и контролировать потоки, которые я развернул. Я понимаю, что могу использовать threadpool для выполнения всей моей выдающейся работы, но я никогда не находил хороший способ отслеживать, сколько работы в данный момент выполняется или что такое статус.

Вместо этого я создаю коллекцию и помещаю в нее потоки после того, как я закручу их - самое последнее, что делает поток, удаляет себя из коллекции. Таким образом, я всегда могу сказать, сколько потоков работает, и я могу использовать коллекцию, чтобы спросить каждого, что она делает. Если есть случай, когда мне нужно их всех убить, обычно вам нужно установить какой-то флаг "Прерывание" в приложении, дождитесь, когда каждый поток заметит это самостоятельно и самозаканчивается - в моем случае, я могут ходить по коллекции и выпускать Thread.Abort для каждой из них.

В этом случае я не нашел лучшего способа работать непосредственно с классом Thread. Как упоминал Эрик Липперт, другие - это просто абстракции более высокого уровня, и ему необходимо работать с классами более низкого уровня, когда доступные реализации высокого уровня не отвечают вашим потребностям. Так же, как иногда вам нужно делать вызовы API Win32, когда .NET не отвечает вашим конкретным потребностям, всегда будут случаи, когда класс Thread является лучшим выбором, несмотря на недавние "достижения".