#c# #wpf #invalidoperationexception
#wpf #многопоточность
Вопрос:
Распространенное исключение, которое можно получить при работе с несколькими потоками в WPF, это:
Вызывающий поток не может получить доступ к этому объекту, поскольку он принадлежит другому потоку
Какие есть варианты, чтобы справиться с этим должным образом?
Ответ №1:
В зависимости от ситуации существуют различные варианты:
Доступ к элементу управления из другого потока
например, обновление текстового блока с информацией о ходе выполнения.
- Привязка данных:
В этом случае самое простое, что вы можете сделать, это избежать прямого взаимодействия с элементом управления. Вы можете просто привязать свойство, к которому хотите получить доступ или изменить, к объекту, класс которого реализует
INotifyPropertyChanged
, а затем вместо этого установить свойство для этого объекта. Фреймворк обработает все остальное за вас. (В общем, вам редко нужно взаимодействовать с элементами пользовательского интерфейса напрямую, вы почти всегда можете связать соответствующие свойства и вместо этого работать с источником привязки; один случай, когда может потребоваться прямой доступ к управлению, — это создание элемента управления.)В некоторых случаях одной привязки данных недостаточно, например, при попытке изменить привязку
ObservableCollection<T>
, для этого вам нужно… - Диспетчеризация:
Вы можете отправить свой код доступа потоку, владеющему объектом, это можно сделать путем вызова
Invoke
илиBeginInvoke
наDispatcher
владельце объекта, к которому осуществляется доступ (получение этогоDispatcher
возможно в другом потоке).например
new Thread(ThisThreadStart).Start();
void ThisThreadStart() { textBlock.Dispatcher.Invoke(new Action(() => textBlock.Text = "Test")); }
Если неясно, в каком потоке выполняется метод, который вы можете использовать
Dispatcher.CheckAccess
для отправки или выполнения действия напрямую.например
void Update() { Action action = () => myTextBlock.Text = "Test"; var dispatcher = myTextBlock.Dispatcher; if (dispatcher.CheckAccess()) action(); else dispatcher.Invoke(action); }
Если объект не является a
DispatcherObject
и вам все еще нужен связанныйDispatcher
, который вы можете использоватьDispatcher.CurrentDispatcher
в потоке, создающем объект (так что выполнение этого в методе, выполняемом потоком, не принесет вам никакой пользы). Для удобства, поскольку вы обычно создаете объекты в основном потоке пользовательского интерфейса приложения; вы можете получить этот потокDispatcher
из любого места, используяApplication.Current.Dispatcher
.
Особые случаи:
BackgroundWorker
Переместите любой доступ к элементу управления
ProgressChanged
так, как это происходит в потоке, создавшем экземпляр (который, конечно, должен быть потоком пользовательского интерфейса)- Таймеры
В WPF вы можете использовать
DispatcherTimer
для удобства, он выполняет диспетчеризацию для вас, поэтому любой кодTick
вызывается в соответствующем диспетчере. Если вы можете делегировать диспетчеризацию системе привязки данных, вы, конечно, можете использовать и обычный таймер.
Вы можете прочитать больше о том, как Dispatcher
работает очередь и потоки WPF в целом в MSDN.
Доступ к объекту, созданному в другом потоке
например, загрузка изображения в фоновом режиме.
Если рассматриваемый объект не Freezable
является таковым, вам вообще следует просто избегать его создания в другом потоке или ограничения доступа к создающему потоку. Если это Freezable
так, вам просто нужно вызвать Freeze
, чтобы сделать его доступным для других потоков.
Доступ к объекту данных из другого потока
То есть тип, экземпляр которого обновляется, является пользовательским кодом. Если возникает исключение, эта ситуация, вероятно, возникла из-за того, что кто-то использовал DependencyObject
в качестве базового типа для класса данных.
Эта ситуация аналогична обращению к элементу управления, и могут применяться те же подходы, но обычно этого следует избегать в первую очередь. Конечно, это позволяет получать простые уведомления об изменении свойств через свойства зависимостей, и эти свойства также могут быть связаны, но достаточно часто это просто не стоит отказа от независимости от потоков. Вы можете получать уведомления об изменениях INotifyPropertyChanged
, и система привязки в WPF по своей сути асимметрична, всегда есть свойство, которое привязано (target) и что-то, что является источником для этой привязки. Обычно пользовательский интерфейс является целью, а данные — источником, что означает, что только компоненты пользовательского интерфейса должны нуждаться в свойствах зависимостей.
Комментарии:
1. Итак, вот глупый вопрос. Что вы делаете, когда диспетчер. checkAccess() выдает сообщение «Вызывающий поток не может получить доступ к этому объекту, поскольку он принадлежит другому потоку»?
2. @RogerWillcocks: Со мной такого никогда не случалось. Приведите полный пример, воспроизводящий это…
3. Приложение. Текущий. Диспетчер. Вызвать (новое действие(() => Текстовое поле. Text = «Test» )); Работал отлично! Спасибо!
Ответ №2:
Это было бы несколько сотен строк кода, для чего-то, что я «понял».
Но резюме таково:
App_OnStartup генерирует фоновый поток
в обратном вызове,
Вызов
Приложение.Текущий.Главное окно.Диспетчер.checkAccess() — получает приложение исключения.Текущий.Диспетчер.checkAccess() не
Комментарии:
1. Проблема в шаге
Current.MainWindow
, а неMainWindow.Dispatcher
в том, так что да, это не то, что я бы предложил…2. Как я понял, но учитывая, что MainWindow и Dispatcher не равны нулю, можно ожидать, что метод, предназначенный для того, чтобы сообщить вам, безопасен ли перекрестный доступ к потоку, не будет генерировать исключение перекрестного потока
Ответ №3:
У меня есть объект прослушивателя udp, который обменивается данными через события, в которых метод / обратные вызовы =’ed в моем файле MainWindow wpf .cs.
Функции обработчика событий вызываются с параметрами, одним из которых является сообщение, которое я хочу отобразить в списке в MainWindow.cs
Используя информацию в этом потоке от H.B. выше; Я добавил, протестировал и обработал перекрестный поток в wpf в моем обратном вызове eventhandler, используя следующий код, но я использую реальное сообщение, а не жестко закодированное:
listBox1.Dispatcher.Invoke(new Action(() => listBox1.Items.Add("MessageHere")));
Обновить:
Это лучше, потому что вы можете поместить больше вещей в анонимную функцию.
listBox1.Dispatcher.Invoke((Action)delegate
{
listBox1.Items.Add(e.ReaderMessage);
});