Ответ 1
Я склонен отвечать на многие вопросы, связанные с многопоточным процессом, и я часто вижу один и тот же основной вопрос, заданный различными способами. Я расскажу о наиболее распространенных проблемах, как я их видел на протяжении многих лет, и объяснил, как более новые технологии облегчили решение этих проблем.
Закрытие переменной цикла
Это не проблема, связанная с потоковой обработкой, но использование потоковой передачи определенно увеличивает проблему. С# 5.0 исправляет эту проблему для цикла foreach
, создавая новую переменную для каждой итерации. Вам больше не придется создавать специальную переменную для закрытия лямбда-выражения. К сожалению, цикл for
все равно должен обрабатываться специальной переменной захвата.
Ожидание выполнения асинхронных задач
В .NET 4.0 представлен класс CountdownEvent
, который инкапсулирует большую логику, необходимую для ожидания выполнения многих задач. Большинство младших разработчиков использовали вызовы Thread.Join
или один вызов WaitHandle.WaitAll
. Оба они имеют проблемы с масштабируемостью. Старый шаблон должен был использовать один ManualResetEvent
и сигнализировать его, когда счетчик достиг нулевого значения. Счетчик был обновлен с использованием класса Interlocked
. CountdownEvent
делает этот шаблон намного проще. Просто не забудьте обработать свою основную роль как работника, чтобы избежать этого тонкого состояния гонки, которое может возникнуть, если один работник закончит работу перед тем, как все работники будут поставлены в очередь.
В .NET 4.0 также был введен класс Task
, который может иметь дочерние задачи, связанные с ним через TaskCreationOptions.AttachedToParent
. Если вы вызываете Task.Wait
на родителя, он будет ждать завершения всех дочерних задач.
Производитель-Потребитель
.NET 4.0 представил класс BlockingCollection
, который действует как обычная очередь, за исключением того, что он может блокировать, когда коллекция пуста. Вы можете поставить в очередь объект, вызвав Add
и удалить объект, вызвав Take
. Take
блокируется, пока элемент не будет доступен. Это значительно упрощает логику производителя и потребителя. Раньше разработчики пытались написать свой собственный класс очереди блокировки. Но, если вы не знаете, что делаете, вы можете действительно испортить... плохо. Фактически, в течение самого долгого времени у Microsoft был пример блокирующей очереди в документации MSDN, которая сама по себе сильно нарушена. К счастью, с тех пор он был удален.
Обновление пользовательского интерфейса с прогрессом рабочего потока
Внедрение BackgroundWorker
сделало отключение фоновой задачи от приложения WinForm намного проще для начинающих разработчиков. Основное преимущество заключается в том, что вы можете вызывать ReportProgress
из обработчика события DoWork
, а обработчики событий ProgressChanged
будут автоматически маршализированы в поток пользовательского интерфейса. Конечно, любой, кто отслеживает мои ответы на SO, знает, как я отношусь к маршалингу (через Invoke
или тому подобное) в качестве решения для обновления пользовательского интерфейса с простой информацией о прогрессе. Я постоянно копирую его, потому что это, как правило, ужасный подход. BackgroundWorker
по-прежнему заставляет разработчика в push-модель (через операции маршалинга в фоновом режиме), но по крайней мере он делает это за кулисами.
Недействительность Invoke
Мы все знаем, что элемент пользовательского интерфейса может быть доступен только из потока пользовательского интерфейса. Обычно это означало, что разработчику приходилось использовать операции маршалинга через ISynchronizeInvoke
, DispatcherObject
или SynchronizationContext
для передачи управления обратно в поток пользовательского интерфейса. Но давайте посмотрим правде в глаза. Эти операции маршалинга выглядят уродливыми. Task.ContinueWith
сделал это немного более изящным, но настоящая слава переходит в await
как часть новой асинхронной модели программирования С# 5. await
может использоваться для ожидания завершения Task
таким образом, чтобы управление потоком временно прерывалось во время выполнения задачи и затем возвращалось именно в этом месте в правильном контексте синхронизации. Нет ничего более элегантного и удовлетворяющего, чем использование await
в качестве замены для всех этих вызовов Invoke
.
Параллельное программирование
Я часто вижу вопросы о том, как все может происходить параллельно. Старый способ состоял в том, чтобы создать несколько потоков или использовать ThreadPool
. В .NET 4.0 использовались TPL и PLINQ. Класс Parallel
- отличный способ получить итерации цикла, идущего параллельно. И PLINQ AsParallel
- это другая сторона той же монеты для простого старого LINQ. Эти новые функции TPL значительно упрощают эту категорию многопоточного программирования.
В .NET 4.5 представлена библиотека потока данных TPL. Он предназначен для создания элегантной и сложной проблемы параллельного программирования. Он абстрагирует классы на блоки. Они могут быть целевыми блоками или исходными блоками. Данные могут поступать из одного блока в другой. Существует много разных блоков, включая BufferBlock<T>
, BroadcastBlock<T>
, ActionBlock<T>
и т.д., Которые все делают разные вещи. И, конечно же, вся библиотека будет оптимизирована для использования с новыми ключевыми словами async
и await
. Это захватывающий новый набор классов, который, как я думаю, будет медленно улавливаться.
Изящное завершение
Как вы можете остановить поток? Я вижу этот вопрос много. Самый простой способ - позвонить Thread.Abort
, но все мы знаем, как это сделать... Надеюсь. Существует много разных способов сделать это безопасно..NET 4.0 представила более унифицированную концепцию, называемую отменой через CancellationToken
и CancellationTokenSource
. Фоновые задачи могут опросить IsCancellationRequested
или просто вызвать ThrowIfCancellationRequested
в безопасных точках, чтобы изящно прервать любую работу, которую они делали. Другие темы могут вызывать Cancel
для запроса отмены.