开发者

Async calls in WP7

I have been experimenting with WP7 apps today and have hit a bit of a wall. I like to have seperation between the UI and the main app code but Ive hit a wall.

I have succesfully implemented a webclient request and gotten a result, but because the call is async I dont know how to pass this backup to the UI level. I cannot seem to hack in a wait for response to complete or anything. I must be doing something wrong.

(this is the xbox360Voice library that I have for download on my website: http://www.jamesstuddart.co.uk/Projects/ASP.Net/Xbox_Feeds/ which I am porting to WP7 as a test)

here is the backend code snippet:

    internal const string BaseUrlFormat = "http://www.360voice.com/api/gamertag-profile.asp?tag={0}";
    internal static string ResponseXml { get; set; }
    internal static WebClient Client = new WebClient();

    public static XboxGamer? GetGamer(string gamerTag)
    {
        var url = string.Format(BaseUrlFormat, gamerTag);

        var response = GetResponse(url, null, null);

        return SerializeResponse(response);
    }

    internal static XboxGamer? SerializeResponse(string response)
    {
        if (string.IsNullOrEmpty(response))
        {
            return null;
        }

        var tempGamer = new XboxGamer();
        var gamer = (XboxGamer)SerializationMethods.Deserialize(tempGamer, response);

        return gamer;
    }

    internal static string GetResponse(string url, string userName, string password)
    {


            if 开发者_Go百科(!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
            {
                Client.Credentials = new NetworkCredential(userName, password);
            }

            try
            {
                Client.DownloadStringCompleted += ClientDownloadStringCompleted;
                Client.DownloadStringAsync(new Uri(url));

                return ResponseXml;
            }
            catch (Exception ex)
            {
                return null;
            }
        }



    internal static void ClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            ResponseXml = e.Result;
        }
    }

and this is the front end code:

public void GetGamerDetails()
{
    var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");
    var xboxGamer = xboxManager.GetGamer();

    if (xboxGamer.HasValue)
    {
        var profile = xboxGamer.Value.Profile[0];
        imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));
        txtUserName.Text = profile.GamerTag;
        txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");
        txtZone.Text = profile.PlayerZone;
    }
    else
    {
        txtUserName.Text = "Failed to load data";
    }
}

Now I understand I need to place something in ClientDownloadStringCompleted but I am unsure what.


The problem you have is that as soon as an asynchronous operation is introduced in to the code path the entire code path needs to become asynchronous.

  • Because GetResponse calls DownloadStringAsync it must become asynchronous, it can't return a string, it can only do that on a callback
  • Because GetGamer calls GetResponse which is now asynchronous it can't return a XboxGamer, it can only do that on a callback
  • Because GetGamerDetails calls GetGamer which is now asynchronous it can't continue with its code following the call, it can only do that after it has received a call back from GetGamer.
  • Because GetGamerDetails is now asynchronous anything call it must also acknowledge this behaviour.
  • .... this continues all the way up to the top of the chain where a user event will have occured.

Here is some air code that knocks some asynchronicity in to the code.

public static void GetGamer(string gamerTag, Action<XboxGamer?> completed) 
{ 
    var url = string.Format(BaseUrlFormat, gamerTag); 

    var response = GetResponse(url, null, null, (response) =>
    {
        completed(SerializeResponse(response));
    }); 
} 


internal static string GetResponse(string url, string userName, string password, Action<string> completed)      
{      

   WebClient client = new WebClient();
   if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))      
   {      
       client.Credentials = new NetworkCredential(userName, password);      
   }      

   try      
   {      
        client.DownloadStringCompleted += (s, args) =>
        {
           // Messy error handling needed here, out of scope
           completed(args.Result);
        };
        client.DownloadStringAsync(new Uri(url));        
   }      
   catch     
   {      
      completed(null);      
   }      
}      


public void GetGamerDetails()              
{              
    var xboxManager = XboxFactory.GetXboxManager("DarkV1p3r");              
    xboxManager.GetGamer( (xboxGamer) =>              
    {
         // Need to move to the main UI thread.
         Dispatcher.BeginInvoke(new Action<XboxGamer?>(DisplayGamerDetails), xboxGamer);
    });

} 

void DisplayGamerDetails(XboxGamer? xboxGamer)
{
    if (xboxGamer.HasValue)              
    {              
        var profile = xboxGamer.Value.Profile[0];              
        imgAvatar.Source = new BitmapImage(new Uri(profile.ProfilePictureMiniUrl));              
        txtUserName.Text = profile.GamerTag;              
        txtGamerScore.Text = int.Parse(profile.GamerScore).ToString("G 0,000");              
        txtZone.Text = profile.PlayerZone;              
    }              
    else              
    {              
        txtUserName.Text = "Failed to load data";              
    }         
}

As you can see async programming can get realy messy.


You generally have 2 options. Either you expose your backend code as an async API as well, or you need to wait for the call to complete in GetResponse.

Doing it the async way would mean starting the process one place, then return, and have the UI update when data is available. This is generally the preferred way, since calling a blocking method on the UI thread will make your app seem unresponsive as long as the method is running.


I think the "Silverlight Way" would be to use databinding. Your XboxGamer object should implement the INotifyPropertyChanged interface. When you call GetGamer() it returns immediately with an "empty" XboxGamer object (maybe with GamerTag=="Loading..." or something). In your ClientDownloadStringCompleted handler you should deserialize the returned XML and then fire the INotifyPropertyChanged.PropertyChanged event.

If you look at the "Windows Phone Databound Application" project template in the SDK, the ItemViewModel class is implemented this way.


Here is how you can expose asynchronous features to any type on WP7.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜