System.UnauthorizedAccessException: Invalid cross-thread access error

Столкнулся с проблемой - в Silverlight нельзя изменять свойства компонент во время обработки результатов какого нить асинхронного вызова (в отдельном потоке). Казалось бы - вроде все одно - контрол и код едины, ни каких постбеков на сервер с передачей состояний нет, а вот и не работает, получаем - System.UnauthorizedAccessException: Invalid cross-thread access error

В Silverlight очень просто это обходится - при помощи класса Dispatcher.

Вырезка из msdn:
"Объект Dispatcher поддерживает очередь рабочих элементов для заданного потока с приоритетами.

Когда для потока создается объект Dispatcher, он становится единственным объектом Dispatcher, который может быть связан с потоком, даже если этотDispatcher остановлен.

При попытке приложения получить CurrentDispatcher для текущего потока, в случае, если с потоком не связан объект Dispatcher, выполняется создание объекта Dispatcher.

Если Dispatcher остановлен, его невозможно перезапустить.

В WPF объект DispatcherObject доступен только для Dispatcher, с которым он связан. Например, фоновый поток не может обновить содержимое элемента управления Button, связанного с Dispatcher потока Пользовательский интерфейс. Чтобы фоновый поток получил доступ к свойству Content элемента управления Button, он должен делегировать работу объекту Dispatcher, связанному с потоком Пользовательский интерфейс. Это делается с помощью метода Invoke или BeginInvoke. Метод Invoke является синхронным, а BeginInvoke — асинхронным. Данная операция добавлена в очередь объектаDispatcher с заданным DispatcherPriority.

Если BeginInvoke вызывается для остановленного Dispatcher, свойству состояния возвращаемого объекта DispatcherOperation присваивается значениеAborted.

Все методы объекта Dispatcher за исключением DisableProcessing являются независимыми от потока."

Итак, теперь становится ясно в чем проблема, и для ее решения необходимо чтобы фоновый поток передал обязанности диспатчеру связанному с пользовательским интерфейсом.

для этого необходимо лишь поместить часть кода в this.Dispatcher.BeginInvoke и больше ничего не менять, для примера я немного изуродовал свой код и представляю ниже:

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
GameState.OnLoggined += (obj, erg) =>
{
GameState.FacebookAPI.Users.GetInfoAsync((info, s, ex) =>
{

this.Dispatcher.BeginInvoke(() =>
{
if (info != null)
litUserName.Text = info[0].name;
else
litUserName.Text = "Guest";
});

}, null);
};

}

В данном примере, на загрузке контрола асинхронно выполняется функция GetInfoAsync, по ее завершении мы имеем данные пользователя, авторизовавшегося в нашем приложении. Я всего навсего хотел было вывести имя пользователя в компоненту litUserName, но, как оказалось, не имел доступа к ним. Для получения доступа мне достаточно было обернуть часть кода в this.Dispatcher.BeginInvoke(() => { /* my code */ } ); и все решилось само собой, т.к. код выполняется теперь в контексте потока отвечающего за пользовательский интерфейс (или типа того)

1 комментарий:

  1. invalid cross-thread access - я сначала даже испугался :)

    ОтветитьУдалить

Можете оставить свой комментарий