Недопустимый межпоточный доступ в приложении Silverlight
Я использую фреймворк Hammock для выполнения асинхронных вызовов служб из приложения Silverlight в службы Rest. В "завершенном" обратном вызове я обновляю ObservableCollection, которая привязана к комбинированному списку в представлении.
Исключение "Недопустимый межпотоковый доступ" создается в обработчике события "OnPropertyChanged".
Это потому, что гамак не выполняет обратный вызов в потоке пользовательского интерфейса? Если нет, то почему нет? Казалось бы, это функциональность, которую должен обрабатывать фреймворк. Я что-то пропустил? Я уверен, что не хочу обрабатывать вызов потока пользовательского интерфейса самостоятельно в каждом завершенном обработчике.
public void LoadMyData()
{
var request = new RestRequest();
request.Path = "MyRestUrlText";
var callback = new RestCallback(
(restRequest, restResponse, userState) =>
{
var visibleData = new ObservableCollection<MyDataType>();
var myData = JsonConvert.DeserializeObject<MyDataType[]> restResponse.Content);
foreach (var item in myData)
visibleData .Add(item);
this.MyBoundCollection = visibleData;
OnPropertyChanged("MyBoundCollection");
});
var asyncResult = _restClient.BeginRequest(request, callback);
}
Спасибо
3 ответа
Для связанных свойств и свойств, которые являются коллекциями (а не дочерними в наблюдаемых коллекциях), только OnPropertyChanged должен быть в потоке пользовательского интерфейса. Свойства могут измениться раньше, но пользовательский интерфейс не изменит привязки, пока не будет вызван OnPropertyChanged.
Все наши ViewModels являются производными от созданной нами ViewModelBase, которая реализует вспомогательный метод SendPropertyChanged, как показано ниже (поэтому нам никогда не придется беспокоиться о кросс-потоке).
Все наши свойства уведомления вызывают это вместо прямого вызова OnPropertyChanged.
Он также предоставляет обычно полезный метод OnUiThread, чтобы вы могли выполнить произвольный код в потоке пользовательского интерфейса:
protected delegate void OnUiThreadDelegate();
public event PropertyChangedEventHandler PropertyChanged;
public void SendPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.OnUiThread(() => this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
}
}
protected void OnUiThread(OnUiThreadDelegate onUiThreadDelegate)
{
if (Deployment.Current.Dispatcher.CheckAccess())
{
onUiThreadDelegate();
}
else
{
Deployment.Current.Dispatcher.BeginInvoke(onUiThreadDelegate);
}
}
Если вы не используете MVVM, а) извинения и б) позор вам:)
Hammock запускает ваш запрос в фоновом потоке, и вы абсолютно хотите выполнить его там и сами переключиться на поток пользовательского интерфейса. В противном случае вы заблокируете поток пользовательского интерфейса, и ваше приложение будет не отвечать на запросы.
Чтобы переключиться обратно на шаг UI, вам нужен дескриптор Dispatcher. Самый простой способ получить это так
Deployment.Current.Dispatcher.BeginInvoke(() => {
this.MyBoundCollection = visibleData;
OnPropertyChanged("MyBoundCollection");
});
Я сделал как ниже
namespace IdleStateDetection
{
public partial class App : Application
{
private bool idle = true;
private System.Threading.Timer _sessionTimeOutTimer = null;
public App()
{
this.Startup += this.Application_Startup;
this.Exit += this.Application_Exit;
this.UnhandledException += this.Application_UnhandledException;
Application.Current.RootVisual.MouseMove += new MouseEventHandler(RootVisual_MouseMove);
Application.Current.RootVisual.KeyDown += new KeyEventHandler(RootVisual_KeyDown);
_sessionTimeOutTimer = new Timer(SessionTimeOutCheck, null, 20000, 60000);
InitializeComponent();
}
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new MainPage();
}
void RootVisual_KeyDown(object sender, KeyEventArgs e)
{
idle = false;
}
void RootVisual_MouseMove(object sender, MouseEventArgs e)
{
idle = false;
}
private void SessionTimeOutCheck(object state)
{
if (Deployment.Current.Dispatcher.CheckAccess())
{
ShowMessage();
}
else
{
Deployment.Current.Dispatcher.BeginInvoke(()=>{ShowMessage();});
}
}
private void ShowMessage()
{
if (idle == true)
{
MessageBox.Show("Idle");
}
}
private void Application_Exit(object sender, EventArgs e)
{
}
private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
// If the app is running outside of the debugger then report the exception using
// the browser's exception mechanism. On IE this will display it a yellow alert
// icon in the status bar and Firefox will display a script error.
if (!System.Diagnostics.Debugger.IsAttached)
{
// NOTE: This will allow the application to continue running after an exception has been thrown
// but not handled.
// For production applications this error handling should be replaced with something that will
// report the error to the website and stop the application.
e.Handled = true;
Deployment.Current.Dispatcher.BeginInvoke(delegate { ReportErrorToDOM(e); });
}
}
private void ReportErrorToDOM(ApplicationUnhandledExceptionEventArgs e)
{
try
{
string errorMsg = e.ExceptionObject.Message + e.ExceptionObject.StackTrace;
errorMsg = errorMsg.Replace('"', '\'').Replace("\r\n", @"\n");
System.Windows.Browser.HtmlPage.Window.Eval("throw new Error(\"Unhandled Error in Silverlight Application " + errorMsg + "\");");
}
catch (Exception)
{
}
}
}
}