Invalid cross-thread access in Silverlight app

2019-05-01 20:53发布

问题:

I am using the Hammock framework to make asyncronous service calls from a Silverlight application to Rest services. In the 'completed' callback I am updating an ObservableCollection that is bound to a combobox on the view.

An 'Invalid cross-thread access' exception is being thrown in the 'OnPropertyChanged' event handler.

Is this becasue Hammock is not executing the callback on the UI thread? If not, why not? That would seem to be functionality that the framework should handle. Am I missing something? I sure do not want to handle the invoking of the UI thread myself in each completed handler.

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

Thanks

回答1:

For bound properties and properties that are collections (and not children in observable collections) it is only the OnPropertyChanged that needs to be on the UI thread. The properties can change earlier, but the UI will not change bindings until OnPropertyChanged is called.

All our ViewModels derive from a ViewModelBase we created that implements a helper SendPropertyChanged like below (so we never have to worry about cross-threading).

All our notify properties call that instead of calling OnPropertyChanged directly.

It also exposes a generally useful OnUiThread method so you can execute arbitrary code on the UI thread:

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

If you are not using MVVM, a) apologies and b) shame on you :)



回答2:

Hammock is running your request on a background thread, and you absolutely want to run it there and handle the switch back to the UI thread yourself. Otherwise you block the UI thread and your application will appear unresponsive.

To switch back to the UI tread you need a handle on the Dispatcher. The easiest way to get it is like so

Deployment.Current.Dispatcher.BeginInvoke(() => {
    this.MyBoundCollection = visibleData;
    OnPropertyChanged("MyBoundCollection");
});


回答3:

I did like below

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)
      {
      }
    }


  }

}