Как бороться с исключениями межпоточного доступа?

#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); 
 });