views:

732

answers:

2

I'm attempting to refactor some moderately complex existing .NET code to be usable in Silverlight. The core issue is that all Silverlight web services calls must be asynch, and the existing code was implemented in a fairly typical synchronous manner.

Existing call stack for a typical operation might be something like this:

Page -> GetPersonMethod -> PersonBusinessObject -> PersonDataObject -> CallWebservice -> (bubble response back up the stack)

My thought is to split all the methods into separate Request and Response methods and preserve the overall structure of the application.

The new call stack for the same operation would then be like this:

Page -> GetPersonRequest -> PersonBusinessRequest -> PersonDataRequest -> WebserviceRequest

Page <- GetPersonResponse <- PersonBusinessResponse <- PersonDataResponse <- WebserviceResponse

Core questions:

  1. Is this a terrible idea, and should I really just rewrite it from the ground up with a more asynch perspective?

  2. Assuming I take this approach how do I preserve the call stack for my nested responses?

TIA-

-Eric

A: 

To avoid refactoring, you might be able to implement CallWebService as a synchronous method that internally uses asynchronous request/response:

  1. After making the asynchronous request, do a WaitOne() on a ManualResetEvent.
  2. In the callback, do a Set() on the ManualResetEvent.

See http://msdn.microsoft.com/en-us/library/system.net.webrequest.endgetrequeststream.aspx

mbeckish
Some flavor of this is theoretically possible, but I've been reading that it's a bad idea do to the fact that Silverlight apps run on a single thread in the browser... opinions?
Scrappydog
If you block the main Silverlight thread, then the UI is going to hang.
MichaelGG
Whether it is SilverLight, WinForms, or ASP.NET, a user's UI thread will hang if blocked. I'm just saying that if blocking the UI thread worked in your previous paradigm, then it might be worth trying it here.
mbeckish
Good point thanks
Scrappydog
+2  A: 
  1. You can't block the main Silverlight thread, or your UI will hang. This is the reason all the network operations in SL are forced async.
  2. You can't touch the UI from any thread but the UI thread.

Those are the two constraints. The way I'd approach this is to create an "async wrapper" function to wrap it up for you. It would take 3 functions (delegates): 1. To execute on a new thread ("f") (Make sure you don't capture any UI objects in your function!) 2. To execute on exception ("econt") 3. To execute on complete ("cont")

Both continuations would be dispatched on the UI thread via System.Windows.Deployment.Current.Dispatcher.BeginInvoke.

With this, you simply need to change your web service calls to be "sync via async", as mbeckish says (use a ManualResetEvent, WaitOne from the sync thread, Set on the callback).

The code for the helper might look something like this (psuedocode, didn't check it):

static void AsyncHelp<T>(Func<T> f, Action<Exception> econt, Action<T> cont) {
  var t = new Thread((_) => {
    try {
      var res = f();
      System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => cont(res));
    } catch (Exception ex) {
      System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => econt(ex));
    }
  });
  t.Start();
}

You would end up using it like this:

some_handler() {
  var id = mytextbox.Text; // Save from UI to local
  AsyncHelp( 
    () => GetBla(id), 
    bla => result.Text = bla.ToString(), // This is safe cause it's dispatched
    ex => error.Text = ex.ToString()
  );
}

Update To make the sync-via-async call, you'd do something like this, assuming you use the default "Event Based Async Pattern" (using BeginXXX/EndXXX is easier, IMO).

Exception ex;
Result r;
var mre = new ManualResetEvent(false);
myService.OnGetBlaCompleted += (_, e) => {
  ex = e.Error;
  r = e.Result;
  mre.Set();
}
myService.GetBlaAsync(id);
mre.WaitOne();
if (ex != null) throw ex;
// and so on
MichaelGG
This is awesome chunck of code, I got it working in a test app in no time flat! Very helpful! Thanks!But I haven't quite got my mind wrapped around how to make your "sync via async" proposal work for async webservices calls... My understanding is that WaitOne() isn't supported in Silverlight?
Scrappydog
http://msdn.microsoft.com/en-us/library/cc190477(VS.95).aspx Seems to indicate WaitOne is there.
MichaelGG