WinForms: Вопрос реализации для того, чтобы мой пользовательский интерфейс работал независимо от моего слоя BLL?
Я пытаюсь сделать приложение для Windows Forms в стиле MVP и - не особо разбираясь с многопоточностью - запутываюсь.
Мой пользовательский интерфейс представляет собой набор очень простых форм. Каждая из форм реализует интерфейс и содержит ссылку на класс-посредник, который находится на уровне бизнес-логики и наоборот. Так как упрощенная схема выглядит так:
CheckInForm : ICheckIn <-------> CheckInMediator : ICheckInMediator
----------------------------------------------------------------------------------------
CheckInForm.Show() <--------
--------> AttemptCheckIn(CheckInInfo)
CheckInForm.DisplayCheckInInfo(DisplayInfo) <--------
--------> CompleteCheckIn(AdditionalCheckInInfo)
PleaseWaitDialog.Show() <--------
PleaseWaitDialog.Close() <--------
CheckInForm.Close() <--------
Как вы можете видеть, классы-посредники управляют пользовательским интерфейсом, сообщая ему, когда отображать данные, запускать, закрывать и т. Д. Они даже указывают, когда должен появиться модальный диалог и когда он должен закрыться (т. Е. PleaseWaitDialog выше). Единственное, что Пользовательский интерфейс показывает данные на экране и ретранслирует вход обратно в посредник.
Эта архитектура приятна и отделена, и ее было очень легко протестировать и создать прототип. Теперь, когда я собираю все это вместе, я начинаю сталкиваться с проблемами потоков. Например, если я хочу, чтобы мой PleaseWaitDialog отображался в виде модальной формы (с использованием ShowDialog()) над CheckInForm, пока таймер, управляемый посредником, не отсчитал 5 секунд (помните, это упрощение), я получу ошибку перекрестного потока если я позвоню PleaseWaitDialog.Close() из обратного вызова таймера. Аналогичным образом, если у меня есть модальное диалоговое окно, блокирующее взаимодействие пользователя с пользовательским интерфейсом, я не хочу, чтобы это блокировало активность на бизнес-уровне, если я не укажу иное (например, в диалоговом окне подтверждения).
Я думаю, что хотел бы запустить посредников и бизнес-логику в основном потоке, а пользовательский интерфейс - в совершенно отдельном потоке, и мой первый вопрос: имеет ли это смысл делать?
Мой второй вопрос: как мне сделать что-то вроде запуска класса в отдельном потоке? И как мне два общаться? Я пробираюсь сквозь чтение по многопоточности.NET, но у меня есть крайний срок, и некоторые примеры того, как заставить класс в главном потоке порождать поток, содержащий пользовательский интерфейс, и заставить их объекты взаимодействовать друг с другом, могут действительно помочь.
2 ответа
Вы смотрели в класс BackgroundWorker? Он отлично подходит для выполнения многих упрощенных процедур в процедурах фонового типа и предоставляет события, которые можно перечислить, чтобы ваш GUI отображал прогресс.
Вы можете управлять элементами управления WinForms из другого потока, но вам нужно использовать Control.Invoke()
и вы будете платить значительную потерю производительности за каждый межпоточный вызов из-за переключения контекста и связанного закулисного CLR voodoo.
Если вы хотите отделить графический интерфейс от бизнес-логики и кода инфраструктуры в многопоточном приложении, я рекомендую перейти на модель обмена сообщениями с использованием потоковобезопасных очередей. Каждый раз, когда нижний уровень (уровни) должен сказать GUI что-то сделать, они помещают объект сообщения в очередь, которую элементы GUI периодически опрашивают через Forms.Timer
, Это особенно хорошо работает для больших приложений, интенсивно использующих процессор, потому что вы можете в некоторой степени регулировать потребности в обработке обновлений GUI, регулируя частоты таймера обновлений.
Для вызовов, возвращающихся в обратном направлении (GUI -> более низкие уровни), вы можете просто вызывать методы-посредники из кода GUI, если эти вызовы возвращаются достаточно быстро - вы должны быть очень осторожны с задержкой потока GUI, потому что пострадает отзывчивость всего приложения. Если у вас есть вызовы, на которые трудно быстро вернуться, вы можете добавить вторую очередь, идущую в другую сторону.