locked
Blocking the UI thread vs. async callbacks RRS feed

  • Question

  • I need to call a web service when the user closes the browser to notify the server. I have an event handler in the window.onbeforeunload event on the javascript side in which I call the web service through a Silverlight method marked as ScriptableMember. Since web services are async in Silverlight, the browser has time to close and kill the Silverlight app before my call can reach the server.

     

    Here’s what I’m trying to do:

     private AutoResetEvent _autoEvent = new AutoResetEvent(false); 

    [ScriptableMember]
    public void OnBrowserClosed()
    {     
        ServiceProxy.LogoutCompleted += ServiceProxy_LogoutCompleted;
       
    ServiceProxy.Logout();
       
    _autoEvent.WaitOne(3000);
    } 

    private void ServiceProxy.LogoutCompleted (object sender, EventArgs e)
    {     
       
    ServiceProxy.LogoutCompleted -= ServiceProxy_LogoutCompleted;
        _autoEvent.Set();
    } 

    This doesn’t work because LogoutCompleted is never fired, probably because the main thread is never notified (see answer from http://silverlight.net/themes/silverlight/forums/thread.aspx?ThreadID=1586&AspxAutoDetectCookieSupport=1). Any ideas on how I could intentionally make the OnBrowserClosed method wait ?

     

    Tuesday, July 21, 2009 2:18 PM

Answers

  • jfrobich,

    I have added a code snippet for your reference and you can do it accordingly for your case.

    The following is a page.xaml.cs code where I have used a BackgroundWorker and also data is retrieved from a service (resembling to your scenario).  

    using System;
    using System.ComponentModel;
    using System.Threading;
    using System.Windows;
    using System.Windows.Controls;

    namespace TestBackgroundWorker {
        public partial class Page : UserControl {
           
            private AutoResetEvent autoEvent = new AutoResetEvent(false);
            private string resultValue = string.Empty;

            public Page() {
                InitializeComponent();

                this.Loaded += new RoutedEventHandler(Page_Loaded);
            }

            void Page_Loaded(object sender, RoutedEventArgs e) {
                BackgroundWorker bw = new BackgroundWorker();
                bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
                bw.DoWork += new DoWorkEventHandler(bw_DoWork);
                bw.RunWorkerAsync();           
            }

            private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
                // Set the result value to a textblock.
                txtResult.Text = resultValue;   
            }

            private void bw_DoWork(object sender, DoWorkEventArgs e) {
                CallService();

                autoEvent.WaitOne();
            }
                   
            private void CallService() {
                // Making a service call here to get the data
                ServiceReference1.Service1Client sc = new ServiceReference1.Service1Client();
                sc.GetDataCompleted += new EventHandler<ServiceReference1.GetDataCompletedEventArgs>(sc_GetDataCompleted);
                sc.GetDataAsync();
            }

            private void sc_GetDataCompleted(object sender, ServiceReference1.GetDataCompletedEventArgs e) {
                if (e.Error == null) {
                    resultValue = e.Result;
                }
                autoEvent.Set();
            }       
        }
    }
     

    Regards,

    Uttam

    Friday, July 24, 2009 2:27 PM

All replies

  • I looked into this problem a few months ago and was unable to find a solution. One thing to remember is that closing the browser is just one way that a connection can be broken. You will also loose connections when cables break, routers fail, power goes out, etc. so you still have to do something on the server end even if you manage to send a logout message when browsers close. The only (unsatisfactory) method that I have come up with so far is to ping clients at random intervals and cull out dead connections as they fail.

    Tuesday, July 21, 2009 3:19 PM
  • You will also loose connections when cables break, routers fail, power goes out, etc.

    One question that I haven't been able to get an answer for is in regards to bi-directional web services. In this case, the http connection is being maintained and repeatedly timed-out, so presumably there should be some way on the server to detect a lost connection. Does anyone have more info on this?

    Tuesday, July 21, 2009 3:34 PM
  • Yes I know, I'm not looking for the silver bullet, I just want to be able to log those cases where the user closes the browser because I'm sure it will be their preferred way of closing the app. I already have a logging upon session timeout on the server as a fallback plan, but I'm looking for a tighter solution. Thanks.

    Tuesday, July 21, 2009 4:10 PM
  • BTW, same thing in Windows Forms, so it's not a Silverlight issue, it's more that I'm missing something on how the "event based asynchronous pattern" works...

    Wednesday, July 22, 2009 10:33 AM
  • Hi,

     The reason for LogoutCompleted is never fired is because "_autoEvent.WaitOne" call in OnBrowserClosed() method blocks the main thread completely.

    To fix this, you can call the following code present inside OnBrowserClosed() in a new thread and also, you need not have 3000 specified as it is not required.

    ServiceProxy.LogoutCompleted += ServiceProxy_LogoutCompleted;
    ServiceProxy.Logout();

    Use background worker thread to make the call in a new thread. 

    Regards,

    Uttam

    Wednesday, July 22, 2009 1:06 PM
  • The solution with the BackgroundWorker indeed works in Windows Forms, but doesn't seem to work in Silverlight.

    Friday, July 24, 2009 10:42 AM
  • It must also work with silverlight too. If you have done the code for silverlight then paste the code here and I will rectify it or else i will give you the code snippet.

    Regards,

    Uttam

    Friday, July 24, 2009 1:12 PM
  • jfrobich,

    I have added a code snippet for your reference and you can do it accordingly for your case.

    The following is a page.xaml.cs code where I have used a BackgroundWorker and also data is retrieved from a service (resembling to your scenario).  

    using System;
    using System.ComponentModel;
    using System.Threading;
    using System.Windows;
    using System.Windows.Controls;

    namespace TestBackgroundWorker {
        public partial class Page : UserControl {
           
            private AutoResetEvent autoEvent = new AutoResetEvent(false);
            private string resultValue = string.Empty;

            public Page() {
                InitializeComponent();

                this.Loaded += new RoutedEventHandler(Page_Loaded);
            }

            void Page_Loaded(object sender, RoutedEventArgs e) {
                BackgroundWorker bw = new BackgroundWorker();
                bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
                bw.DoWork += new DoWorkEventHandler(bw_DoWork);
                bw.RunWorkerAsync();           
            }

            private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
                // Set the result value to a textblock.
                txtResult.Text = resultValue;   
            }

            private void bw_DoWork(object sender, DoWorkEventArgs e) {
                CallService();

                autoEvent.WaitOne();
            }
                   
            private void CallService() {
                // Making a service call here to get the data
                ServiceReference1.Service1Client sc = new ServiceReference1.Service1Client();
                sc.GetDataCompleted += new EventHandler<ServiceReference1.GetDataCompletedEventArgs>(sc_GetDataCompleted);
                sc.GetDataAsync();
            }

            private void sc_GetDataCompleted(object sender, ServiceReference1.GetDataCompletedEventArgs e) {
                if (e.Error == null) {
                    resultValue = e.Result;
                }
                autoEvent.Set();
            }       
        }
    }
     

    Regards,

    Uttam

    Friday, July 24, 2009 2:27 PM