Во многих местах я видел, что каноническая мудрость 1 заключается в том, что вызывающая сторона несет ответственность за обеспечение того, чтобы вы были в потоке пользовательского интерфейса при обновлении компонентов пользовательского интерфейса (в частности, в Java Swing, что вы находитесь в потоке диспетчеризации событий ) ,
Почему это так? Поток диспетчеризации событий является проблемой представления в MVC / MVP / MVVM; обрабатывать его где угодно, но представление создает тесную связь между реализацией представления и моделью потоков этого представления.
В частности, допустим, у меня есть MVC-приложение, использующее Swing. Если вызывающая сторона отвечает за обновление компонентов в потоке диспетчеризации событий, то, если я пытаюсь поменять свою реализацию Swing View на реализацию JavaFX, я должен изменить весь код Presenter / Controller, чтобы вместо этого использовать поток приложения JavaFX .
Итак, я полагаю, у меня есть два вопроса:
- Почему ответственность за обеспечение безопасности потоков компонентов пользовательского интерфейса лежит на вызывающей стороне? Где недостаток в моих рассуждениях выше?
- Как я могу спроектировать свое приложение так, чтобы оно не связывало эти проблемы с безопасностью потоков, но при этом оставалось бы соответствующим образом ориентированным на многопотоковое исполнение?
Позвольте мне добавить некоторый Java-код MCVE, чтобы проиллюстрировать, что я подразумеваю как «ответственный за вызывающего абонента» (здесь есть и другие полезные практики, которые я не выполняю, но я стараюсь быть как можно меньше):
Звонящий ответственен:
public class Presenter {
private final View;
void updateViewWithNewData(final Data data) {
EventQueue.invokeLater(new Runnable() {
public void run() {
view.setData(data);
}
});
}
}
public class View {
void setData(Data data) {
component.setText(data.getMessage());
}
}
Посмотреть ответственность:
public class Presenter {
private final View;
void updateViewWithNewData(final Data data) {
view.setData(data);
}
}
public class View {
void setData(Data data) {
EventQueue.invokeLater(new Runnable() {
public void run() {
component.setText(data.getMessage());
}
});
}
}
1: Автор этого поста имеет наивысшую оценку в Swing на переполнении стека. Он говорит это повсюду, и я также видел, что это ответственность за звонящего в других местах.
источник
Ответы:
В конце своего неудавшегося эссе о мечте Грэм Хэмилтон (крупный Java-архитектор) упоминает, что если разработчики «хотят сохранить эквивалентность с моделью очереди событий, им нужно будет следовать различным неочевидным правилам» и иметь видимые и явные Модель очереди событий «кажется, помогает людям более надежно следовать модели и таким образом создавать программы с графическим интерфейсом, которые работают надежно».
Другими словами, если вы попытаетесь поместить многопоточный фасад поверх модели очереди событий, абстракция будет иногда просачиваться неочевидными способами, которые чрезвычайно трудно отладить. Кажется, что это будет работать на бумаге, но в конечном итоге рушится в производстве.
Добавление небольших оболочек вокруг отдельных компонентов, вероятно, не будет проблематичным, например, обновление индикатора выполнения из рабочего потока. Если вы попытаетесь сделать что-то более сложное, требующее многократных блокировок, станет очень трудно рассуждать о взаимодействии многопоточного слоя и слоя очереди событий.
Обратите внимание, что такого рода проблемы являются универсальными для всех инструментов GUI. Предположив о событии отправки модели в вашем ведущий / контроллер не плотно сцепления вам только один конкретный параллелизм модель GUI инструментария, это соединения вас всех из них . Интерфейс очереди событий не должен быть таким сложным для абстракции.
источник
Потому что обеспечение безопасности потока GUI-библиотеки - это огромная головная боль и узкое место.
Поток управления в графических интерфейсах часто идет в двух направлениях: от очереди событий до корневого окна к графическим элементам и от кода приложения до виджета, распространяемого до корневого окна.
Разработать стратегию блокировки, которая не блокирует корневое окно (вызовет много споров), сложно . Блокировка снизу вверх, в то время как другая нить блокируется сверху вниз, является отличным способом достижения мгновенного тупика.
А проверка того, является ли текущий поток потоком GUI, каждый раз является дорогостоящей и может привести к путанице в отношении того, что на самом деле происходит с графическим интерфейсом, особенно когда вы выполняете последовательность чтения и записи обновления. Это должно иметь блокировку данных, чтобы избежать гонок.
источник
Многопоточность (в модели с общей памятью) - это свойство, которое не поддается абстракции. Простым примером является
Set
-тип: хотяContains(..)
иAdd(...)
иUpdate(...)
является совершенно допустимым API в однопотоковом сценарии, многопоточный сценарий нуждается вAddOrUpdate
.То же самое относится и к пользовательскому интерфейсу - если вы хотите отобразить список элементов с количеством элементов в верхней части списка, вам необходимо обновлять оба элемента при каждом изменении.
Ни один из этих взглядов не выглядит действительно заманчивым. Назначать докладчика ответственным за обработку потоков рекомендуется не потому, что это хорошо, а потому, что он работает, а альтернативы хуже.
источник
System.Windows.Threading.Dispatcher
возможность обрабатывать потоки как в WPF, так и в WinForms. Такой слой между докладчиком и представлением, безусловно, полезен. Но это только обеспечивает независимость инструментария, а не независимость от потоков.Некоторое время назад я прочитал действительно хороший блог, в котором обсуждается эта проблема (упомянутая Карлом Билефельдом), и в основном он говорит о том, что очень опасно пытаться сделать поток комплекта пользовательского интерфейса безопасным, так как он вводит возможные взаимоблокировки и зависит от того, как Реализованы условия гонки в рамках.
Существует также соображение производительности. Не так много сейчас, но когда Swing был впервые выпущен, его сильно критиковали за его производительность (была плохая), но это не было на самом деле виной Swing, это было отсутствие у людей знаний о том, как его использовать.
SWT реализует концепцию безопасности потоков, создавая исключения, если вы нарушаете это, не очень, но, по крайней мере, вы узнали об этом.
Например, если вы посмотрите на процесс рисования, порядок, в котором элементы окрашиваются, очень важен. Вы не хотите, чтобы рисование одного компонента имело побочный эффект на любой другой части экрана. Представьте, что вы могли бы обновить свойство текста метки, но оно было нарисовано двумя разными потоками, что может привести к повреждению вывода. Таким образом, все рисование выполняется в одном потоке, обычно в соответствии с порядком требований / запросов (но иногда сокращается, чтобы уменьшить количество реальных физических циклов рисования)
Вы упомянули о переходе с Swing на JavaFX, но у вас возникла бы эта проблема практически с любой инфраструктурой пользовательского интерфейса (не только с толстыми клиентами, но и с веб-интерфейсом), но Swing, кажется, является той, которая подчеркивает эту проблему.
Вы могли бы разработать промежуточный уровень (контроллер контроллера?), Задачей которого является обеспечение правильной синхронизации вызовов в пользовательском интерфейсе. Невозможно точно знать, как вы могли бы проектировать свои части API, не относящиеся к пользовательскому интерфейсу, с точки зрения API пользовательского интерфейса, и большинство разработчиков будут жаловаться, что любая защита потоков, реализованная в API пользовательского интерфейса, была ограничительной или не отвечала их потребностям. Лучше позволить вам решить, как вы хотите решить эту проблему, исходя из своих потребностей
Одна из самых больших проблем, которую вам необходимо рассмотреть, - это возможность обосновать данный порядок событий на основе известных входных данных. Например, если пользователь изменяет размер окна, модель очереди событий гарантирует, что будет происходить заданный порядок событий, это может показаться простым, но если очередь допускает события, запускаемые другими потоками, вы больше не можете гарантировать порядок в какие события могут произойти (состояние гонки), и внезапно вы должны начать беспокоиться о разных состояниях и ничего не делать, пока не произойдет что-то другое, и вам не придется делиться флагами состояний вокруг, и в итоге вы получите спагетти.
Хорошо, вы могли бы решить эту проблему, имея какую-то очередь, которая упорядочивала события в зависимости от времени их выпуска, но разве это не то, что у нас уже есть? Кроме того, вы все еще не можете гарантировать, что поток B будет генерировать свои события ПОСЛЕ потока A
Основная причина, по которой люди расстраиваются из-за того, что им приходится думать о своем коде, заключается в том, что их заставляют думать о своем коде / дизайне. "Почему это не может быть проще?" Это не может быть проще, потому что это не простая проблема.
Я помню, когда была выпущена PS3, и Sony активно рассказывала о процессоре Cell и о его способности выполнять отдельные строки логики, декодировать аудио, видео, загружать и разрешать данные модели. Один из разработчиков игр спросил: «Это все круто, но как вы синхронизируете потоки?»
Проблема, о которой говорил разработчик, заключалась в том, что в какой-то момент все эти отдельные потоки должны были быть синхронизированы до одного канала для вывода. Бедный ведущий просто пожал плечами, поскольку это была не та проблема, с которой они были знакомы. Очевидно, у них уже есть решения для решения этой проблемы, но в то время это было забавно.
Современные компьютеры принимают много информации одновременно из множества разных мест, все эти данные должны быть обработаны и доставлены пользователю на расстоянии, что не мешает представлению другой информации, поэтому это сложная проблема без единственное простое решение.
Теперь, имея возможность переключать фреймворки, это непросто спроектировать, НО, на мгновение возьмем MVC, MVC может быть многоуровневым, то есть у вас может быть MVC, который непосредственно связан с управлением фреймворком UI, вы затем можно обернуть это, опять же, в MVC более высокого уровня, который имеет дело с взаимодействиями с другими (потенциально многопоточными) структурами, этот уровень будет обязан определять, как нижний уровень MVC уведомляется / обновляется.
Затем вы будете использовать кодирование для сопряжения шаблонов проектирования и шаблонов фабрики или сборщика для создания этих различных слоев. Это означает, что многопоточные фреймворки будут отделены от уровня пользовательского интерфейса за счет использования среднего уровня в качестве идеи.
источник