The context of anonymous methods/lambda’s is lost on async proxy calls in SL3 (Updated)

EDIT: This issue was caused by using cached singletons for each proxy (see comments). So the context of anonymous method works correctly, it just appeared it didn’t because multiple event handler (lambda’s) were registered to the same proxy instance.

We’re building an SL3 application that gets its data from a WCF (Ria) Service. The application uses a standard generated proxy (SlSvcUtil.exe) and all calls are done using the familiar async pattern: call a begin method and wait for the completed event. In our SL3 application we have some layers on top of this Proxy:

1) The ServiceAgent – manages the service proxy and transparently recreates it when it is in a Faulted state. It subscribes to the completed event on the proxy and promotes this to the caller (repositories).
2) The Repositories – exposes centralized and cached data access divided bij functional area / domain.
3) The Model (per module) – Each module in the app implements the MVVM pattern and its model accesses the repositories and maps the data from service contracts to module-specific view entities.

Because all data fetching is async we use an eventing mechanism (similar to INotifyPropertyChanged and INotifyCollectionChanged) to communicate’the ‘completed’ event from the proxy upward through the application.

It was in the implementation of the repositories that I first discovered that something was wrong with the context in which our ‘completed’ events were raised. Our implementation looked something like this (pseudo code):

public DataEntity GetDataEntity(Guid Id)
{
    DataEntity retVal = new DataEntity();

    using(var proxy = _serviceAgent.GetProxy<RequestDataContract, ResponseDataContract>())
    {
        proxy.RegisterCompletedHandler( (response) =>
            {
                retVal.Property1 = Response.Property1;
                retVal.Property2 = Response.Property2;
                retVal.Property3 = Response.Property3;
            });
        proxy.BeginRequest(new Request(id));
    }
    return retVal;
}

Initially this method returns an empty object that gets mapped to an empty ViewEntity and bound to the UI. When the proxy reports the service call is completed the ‘CompletedHandler’ is called implemented by the lambda and provides access to the response of the service call. Now the empty DataEntity is filled and these changes are propegated to the ViewEntity (think INotifyPropertyChanged) and in turn the Viewentity notifies its changes to the UI (also INotifyPropertyChanged). This works, no problem.

Untill you place another call to the same repository method while the first is still ‘running’. Then the ‘context’ the lambda needs to fill the retVal is lost and ‘overwritten’ by the second call. So it may be very well that the result of the first call is written to the retVal of the second call. You can imagine the strange behavior you’ll get in your app (and how long it takes to figure out what the problem is ;-).

The solution that I’ve found is to use the userState that the proxy allows you to sent with a method call. The pseudo code will look something like this:

public DataEntity GetDataEntity(Guid Id)
{
    DataEntity retVal = new DataEntity();

    using(var proxy = _serviceAgent.GetProxy<RequestDataContract, ResponseDataContract>())
    {
        proxy.RegisterCompletedHandler( (response, userState) =>
            {
                DataEntity de = (DataEntity)userState;
                de.Property1 = Response.Property1;
                de.Property2 = Response.Property2;
                de.Property3 = Response.Property3;
            });
        proxy.BeginRequest(new Request(id), retVal);
    }
    return retVal;
}

Now the correct retVal is passed as userState allong with the service call to the proxy and the completed (event) handler will now have access to it when it is called and will be able to set the property values.

I was very suprised that this occurred in my code and it may very well be that I’m doing things wrong, but I don’t see it. Any suggestions are welcome.

Hope it helps.