views:

44

answers:

1

I have a silverlight client that communicates with a web service on a server. It has a DoSomething method that does nothing and returns void.

On the client, I call the service and listen to when the response comes back:

proxy.OnDoSomethingCompleted+=OnDoSomethingCompleted;
t0 = Environment.TickCount;
proxy.DoSomethingAsync();

void DoSomething(..)
{
    t1 = Environment.TickCount;
}

Network capture indicates the response is sent back within 2ms. However, OnDoSomethingCompleted is not called until 80ms later. Is there a way to change when the callback is executed?

A: 

Normally, OnDoSomethingCompleted() would be executed on the UI thread, i.e., behind the scenes, something is calling some code that (conceptually) looks a little like this:

Dispatcher.BeginInvoke(() => OnDoSomethingCompleted());

This means that OnDoSomethingCompleted() won't get executed until the UI thread decides to cooperate and run it. Most of the time that's fine, but there can be times when you want it to run faster. The basic approach is to use a thread pool to make the original call, which means that the response will get handled from the same thread pool (not necessarily ont he same thread). If you can do some real processing in this return method, and don't just automatically marshal it back onto the UI thread, this can speed up your processing somewhat.

Tomek (from the MS WCF team) gives a good example of how do this here:

http://tomasz.janczuk.org/2009/08/improving-performance-of-concurrent-wcf.html

It's also my understanding that the synchronization context for the WCF connection gets set when you first open it. This means that whatever thread the WCF connection is first opened on is the one that will handle all later calls. So in my own code, I do something like this:

// Spin up the connection on a new worker thread.
// According to Tomek, this will cause all WCF calls to be made from this thread.
ManualResetEvent resetEvent = new ManualResetEvent(false);
wcfWorkerThread = new Thread(new ThreadStart(() => InitializeNotificationClient(resetEvent)));
wcfWorkerThread.Name = "WcfWorkerThread";
wcfWorkerThread.Start();
resetEvent.WaitOne();

And then InitializeNotificationClient() looks something like this:

private void InitializeNotificationClient(ManualResetEvent resetEvent = null)
{
    try
    {
        notificationClient = GetRoomServiceClient();
        notificationClient.OpenAsync(callback);
        notificationClient.InnerChannel.Faulted += new EventHandler(Channel_Faulted);
        notificationClient.InnerChannel.Closed += new EventHandler(Channel_Closed);
     }
     finally
     {
         // Tell the waiting thread that we're ready.
         if (resetEvent != null)
         {
             resetEvent.Set();
         }
     }
 }
Ken Smith
The callback is already executed on a worker thread. This blog shows the performance improvement when using concurrent web service calls. The duration of each call is still the same so there will be no improvement if calls must be done sequentially (for eg, when result in 1st call is input to 2nd call)
Are you using Net.TCP, or the BasicHttpBinding that's the default? If you're concerned with performance, Net.TCP is obviously the way to go. If you have other reasons for using HTTP, though, you may want to look at using WebRequest.RegisterPrefix() to switch over to the Silverlight HTTP stack (instead of the browser's). It's supposedly faster, though I haven't timed it myself. http://msdn.microsoft.com/en-us/library/dd920295(VS.95).aspx
Ken Smith
Also, one other thing. You'll want to ensure that you're opening the connection from a separate worker thread. According to Tomek (from a conversation we had), opening the connection from a specific thread will cause all later WCF calls to be marshaled to that thread. See my (edited) answer for some code.
Ken Smith