Какой подход использовать с этой многопоточностью? - PullRequest
2 голосов
/ 13 августа 2010

Короткий вопрос :

Я хотел бы создать отдельный фоновый поток, который обрабатывал бы рабочие элементы, переданные в очередь (например, пул потоков с одним потоком).Некоторые из рабочих элементов способны сообщать о прогрессе, некоторые нет.Какой из множества подходов многопоточности .NET мне следует использовать?


Длинное объяснение (чтобы не спрашивать о половине , которая не имеет никакого смысла ):

Главное окно моего приложения winforms разделено по вертикали на две половины.Левая половина содержит дерево с элементами.Когда пользователь дважды щелкает элемент в древовидном меню, элемент открывается в правой половине.Почти все объекты имеют множество свойств, разбитых на несколько разделов (представлены вкладками).Загрузка этих свойств занимает довольно много времени, обычно около 10 секунд, иногда больше.Время от времени добавляются дополнительные свойства, поэтому время увеличивается.

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

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

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

A ThreadPool выполняет управление очередью рабочих элементов, но средств для этого нетотчет о проделанной работе.BackgroundWorker, с другой стороны, поддерживает отчеты о проделанной работе, но он предназначен для одного рабочего элемента.Возможно ли сочетание обоих?

Ответы [ 3 ]

2 голосов
/ 13 августа 2010

.NET 4.0 вносит множество улучшений в многопоточность, вводя тип Task, представляющий одну, возможно, асинхронную операцию.

Для вашего сценария я бы рекомендовал разделить загрузку каждогосвойство (или группа свойств) на отдельные задачи.Задачи включают понятие «родитель», поэтому загрузка каждого объекта может быть родительской задачей, владеющей задачами загрузки свойств.

Для обработки отмены используйте новую унифицированную структуру отмены.Создайте CancellationTokenSource для каждого объекта и передайте его CancellationToken родительской задаче (которая передает его каждой дочерней задаче).Это позволяет отменить объект, который может вступить в силу после выполнения свойства, загружаемого в данный момент (вместо ожидания, пока весь объект не будет завершен).

Для обработки параллелизма (или, более правильно, non -concurrency), используйте OrderedTaskScheduler из библиотеки образцов ParallelExtensionsExtras .Каждый Task представляет только единицу работы, которую необходимо запланировать, и, используя OrderedTaskScheduler, вы обеспечиваете последовательное выполнение (в потоке ThreadPool).

Обновления пользовательского интерфейса можно выполнить, создав пользовательский интерфейс.обновите Task и запланируйте его в потоке пользовательского интерфейса.У меня есть пример этого в моем блоге , где я обертываю некоторые из более неуклюжих методов в ProgressReporter вспомогательный тип.

Одна приятная вещь о типе Task состоит в том, чтоон распространяет исключения и отмены естественным образом;это часто самые трудные части разработки системы для решения проблемы, подобной вашей.

1 голос
/ 13 августа 2010

Используйте поток, поместите свою работу в потокобезопасную коллекцию и используйте invoke, когда вы обновляете свой пользовательский интерфейс, чтобы сделать это в нужной теме

1 голос
/ 13 августа 2010

Звучит сложно!

Вы говорите, что ваш источник данных не является потокобезопасным. Итак, что это значит для пользователя. Если они щелкают повсюду, но не ждут загрузки свойств, прежде чем щелкнуть где-то еще, они могут щелкнуть по 10 узлам, которые долго загружаются, и затем сидеть в ожидании 10-го. Загрузка должна выполняться одна за другой, поскольку доступ к источнику данных не является потокобезопасным. Это указывает на то, что ThreadPool не будет хорошим выбором, поскольку он будет запускать нагрузки параллельно и нарушать безопасность потоков. Было бы хорошо, если бы загрузка могла быть прервана на полпути, чтобы пользователю не приходилось ждать загрузки последних 9 узлов, прежде чем начнется загрузка страницы, которую он хочет видеть.

Если загрузка может быть прервана, я бы посоветовал использовать BackgroundWorker. Если пользователь переключает узел, и BackgroundWorker уже занят, установите событие или что-то, сигнализирующее, что оно должно прервать существующую работу, а затем поставьте в очередь новую работу для загрузки текущей страницы.

Кроме того, учтите, что запуск потока в пуле потоков не слишком сложен. Для этого передайте объект прогресса в вызов QueueUserWorkItem типа, подобного следующему:

class Progress
{
  object _lock = new Object();
  int _current;
  bool _abort;

  public int Current
  {
    get { lock(_lock) { return _current; } }
    set { lock(_lock) { _current = value; } }
  }

  public bool Abort
  {
    get { lock(_lock) { return _abort; } }
    set { lock(_lock) { _abort = value; } }
  }
}

Поток может писать в это, а поток пользовательского интерфейса может опрашивать (из события System.Windows.Forms.Timer), чтобы прочитать прогресс и обновить индикатор выполнения или анимацию.

Кроме того, если вы включите свойство Abort. Пользовательский интерфейс может установить его, если пользователь меняет узел. Метод load может в различных точках на протяжении всей своей работы проверять значение сброса и, если он установлен, возвращаться без завершения загрузки.

Если честно, то, что вы выбираете, не имеет никакого значения. Все три варианта делают вещи в фоновом потоке. На вашем месте я бы начал с BackgroundWorker, так как он довольно прост в настройке, и если вы решите, что вам нужно что-то еще, подумайте о том, чтобы потом переключиться на ThreadPool или обычный поток.

BackgroundWorker также имеет то преимущество, что вы можете использовать его завершенное событие (которое выполняется в основном потоке пользовательского интерфейса) для обновления пользовательского интерфейса загруженными данными.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...