3

I am learning to use the Rx extensions for a Silverlight 4 app I am working on. I created a sample app to nail down the process and I cannot get it to return anything. Here is the main code:

    private IObservable<Location> GetGPSCoordinates(string Address1)
    {
        var gsc = new GeocodeServiceClient("BasicHttpBinding_IGeocodeService") as IGeocodeService;

        Location returnLocation = new Location();
        GeocodeResponse gcResp = new GeocodeResponse();

        GeocodeRequest gcr = new GeocodeRequest();
        gcr.Credentials = new Credentials();
        gcr.Credentials.ApplicationId = APP_ID2;
        gcr.Query = Address1;

        var myFunc = Observable.FromAsyncPattern<GeocodeRequest, GeocodeResponse>(gsc.BeginGeocode, gsc.EndGeocode);
        gcResp = myFunc(gcr) as GeocodeResponse;

        if (gcResp.Results.Count > 0 && gcResp.Results[0].Locations.Count > 0)
        {
            returnLocation = gcResp.Results[0].Locations[0];
        }
        return returnLocation as IObservable<Location>;
    }

gcResp comes back as null. Any thoughts or suggestions would be greatly appreciated.

3 Answers3

1

The observable source you are subscribing to is asynchronous, so you can't access the result immediately after subscribing. You need to access the result in the subscription.

Better yet, don't subscribe at all and simply compose the response:

private IObservable<Location> GetGPSCoordinates(string Address1)
{
    IGeocodeService gsc = 
        new GeocodeServiceClient("BasicHttpBinding_IGeocodeService");

    Location returnLocation = new Location();
    GeocodeResponse gcResp  = new GeocodeResponse();

    GeocodeRequest gcr = new GeocodeRequest();
    gcr.Credentials = new Credentials();
    gcr.Credentials.ApplicationId = APP_ID2;
    gcr.Query = Address1;

    var factory = Observable.FromAsyncPattern<GeocodeRequest, GeocodeResponse>(
        gsc.BeginGeocode, gsc.EndGeocode);

    return factory(gcr)
        .Where(response => response.Results.Count > 0 && 
                           response.Results[0].Locations.Count > 0)
        .Select(response => response.Results[0].Locations[0]);
}

If you only need the first valid value (the location of the address is unlikely to change), then add a .Take(1) between the Where and Select.

Edit: If you want to specifically handle the address not being found, you can either return results and have the consumer deal with it or you can return an Exception and provide an OnError handler when subscribing. If you're thinking of doing the latter, you would use SelectMany:

return factory(gcr)
    .SelectMany(response => (response.Results.Count > 0 && 
        response.Results[0].Locations.Count > 0)
        ? Observable.Return(response.Results[0].Locations[0])
        : Observable.Throw<Location>(new AddressNotFoundException())
    );
Richard Szalay
  • 83,269
  • 19
  • 178
  • 237
  • Richard, That worked - Thanks a lot. One more question - if the address is invalid, then, based on the where clause, the function does not return anything. So it would seem to be better to have my function return IObservable and then be able to test the results count in the Subscribe. Would you have any other suggestion? Thanks a lot for your assistance. – AussieAtHeart Mar 17 '11 at 08:23
  • @AussieAtHeart - Hi Aussie, any responses to individual answers should be added as a comment rather than a separate answer. You should consider moving this to a comment on Richards Answer. – James Hay Mar 17 '11 at 08:37
  • @James Hay, I am only able to add a comment to this post, not to any other post on this thread, even though I am logged in. I'm not sure why this is happening. – AussieAtHeart Mar 17 '11 at 15:58
  • @AussieAtHeart - You should be able to comment on answers to your own question. The problem is that you have created multiple accounts. The account that you added this answer with (663939) is different from that of your original question (663488) and different again from your other (deleted) answer (663683). You should log back in with your original account. – Richard Szalay Mar 17 '11 at 16:36
  • @AussieAtHeart: BTW, if you have accidentally created multiple accounts, you can ask the moderators to merge them. See [How can one link/merge/combine/associate two accounts/users? (Anonymous/unregistered/cookie or OpenID/registered)](http://meta.stackexchange.com/q/18232/131247) – Helen Mar 17 '11 at 18:24
0

If you expand out the type of myFunc you'll see that it is Func<GeocodeRequest, IObservable<GeocodeResponse>>.

Func<GeocodeRequest, IObservable<GeocodeResponse>> myFunc =
    Observable.FromAsyncPattern<GeocodeRequest, GeocodeResponse>
        (gsc.BeginGeocode, gsc.EndGeocode);

So when you call myFunc(gcr) you have an IObservable<GeocodeResponse> and not a GeocodeResponse. Your code myFunc(gcr) as GeocodeResponse returns null because the cast is invalid.

What you need to do is either get the last value of the observable or just do a subscribe. Calling .Last() will block. If you call .Subscribe(...) your response will come thru on the call back thread.

Try this:

gcResp = myFunc(gcr).Last();

Let me know how you go.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
0

Richard (and others),

So I have the code returning the location and I have the calling code subscribing. Here is (hopefully) the final issue. When I call GetGPSCoordinates, the next statement gets executed immediately without waiting for the subscribe to finish. Here's an example in a button OnClick event handler.

Location newLoc = new Location();

GetGPSCoordinates(this.Input.Text).ObserveOnDispatcher().Subscribe(x =>
            {
             if (x.Results.Count > 0 && x.Results[0].Locations.Count > 0)
                  {
                     newLoc = x.Results[0].Locations[0];
                     Output.Text = "Latitude: " + newLoc.Latitude.ToString() +
  ", Longtude: " + newLoc.Longitude.ToString();
                  }
             else
                  {
                    Output.Text = "Invalid address";
                  }
});
            Output.Text = " Outside of subscribe --- Latitude: " + newLoc.Latitude.ToString() +
  ", Longtude: " + newLoc.Longitude.ToString();

The Output.Text assignment that takes place outside of Subscribe executes before the Subscribe has finished and displays zeros and then the one inside the subscribe displays the new location info.

The purpose of this process is to get location info that will then be saved in a database record and I am processing multiple addresses sequentially in a Foreach loop. I chose Rx Extensions as a solution to avoid the problem of the async callback as a coding trap. But it seems I have exchanged one trap for another.

Thoughts, comments, suggestions?

  • (This should have been an edit to your original question, but I realise that you can't do that until your multiple-account situation is resolved by the moderators) – Richard Szalay Mar 17 '11 at 21:01
  • The code after the `Subscribe` executes immedietly because Subscribe is non-blocking. You cannot avoid the fact that you are working asynchronously, all you can do is work with it by moving the code into the subscribe handler. – Richard Szalay Mar 17 '11 at 21:02
  • But then how do you conceptually handle this situation? If someone has a list of addresses and wants to convert them to GPS coordinates or a list of words they want to translate, what is the best way to handle it? The only other option I have found is the Async CTP, which is not ready for prime time yet. Are there any examples around? I have searched here and on other sites and have not found anything. – AussieAtHeart Mar 17 '11 at 21:35
  • I have done some more research and it looks like chaining using SelectMany in Rx is my best bet. Any other suggestions are welcome. – AussieAtHeart Mar 17 '11 at 22:04
  • Chaining using `SelectMany` is indeed a common choice. You can also use ForkJoin to handle multiple parallel calls. – Richard Szalay Mar 17 '11 at 22:19