简体   繁体   中英

HttpClient doesn't return in Windows Phone

Simple enough, here's my code:

/// <summary>
/// Fetches the JSON string from the URL.
/// </summary>
/// <param name="url">URL to download the JSON from.</param>
/// <returns>JSON formatted string</returns>
private static string GetJsonResponse(string url)
{
    var result = DownloadString(url).Result;
    System.Diagnostics.Debug.WriteLine("Downloaded from {0} with result = {1}", url, result);
    return result;
}

private static async Task<string> DownloadString(string feedUrl)
{
    var result = "";
    System.Diagnostics.Debug.WriteLine("Downloading from {0}", feedUrl);
    using (var client = new HttpClient())
    {
        result = await client.GetStringAsync(new Uri(feedUrl, UriKind.Absolute));
        result.Trim();
        System.Diagnostics.Debug.WriteLine("Finished download from {0}", feedUrl);
    }
    return result;
}

it prints the first string but there's no way to get to second one, and this means that the client.GetStringAsync doesn't return.

The URL is ok, it loads fine.

What's my problem?

EDIT 2: (Removed as useless now)

EDIT 3:

I'm adding the methods that I'm using to let you know what's the flow of my data.

Page.xaml

<phone:LongListSelector Grid.Row="1" x:Name="UpcomingResultsList" ItemsSource="{Binding UpcomingMovies}" ItemTemplate="{StaticResource MovieListDataTemplate}" Margin="0, 10, 0, 0" />

UpcomingMovies is bound to this property

public List<MovieViewModel> UpcomingMovies
    {
        get
        {
            System.Diagnostics.Debug.WriteLine("UpcomingMovies - Begin");             
            var apiMovies = _serviceRT.FindUpcomingMoviesList().Result; // We get the movies in RT API's format
            var movies = apiMovies.Select(result => new MovieViewModel(result)).ToList();
            System.Diagnostics.Debug.WriteLine("UpcomingMovies - End");
            return movies;
        }
}

the _serviceRT.FindUpcomingMoviesList() method:

/// <summary>
/// Gets a list of upcoming movies.
/// </summary>
/// <returns>MovieSearchResults</returns>
public async Task<MovieSearchResults> FindUpcomingMoviesList()
{
    var url = string.Format(LIST_UPCOMING, ApiKey);
    var jsonResponse = await GetJsonResponse(url);
    var results = Parser.ParseMovieSearchResults(jsonResponse);
    return results;
}

Finally, the GetJsonResponse() is at the beginning of the question.

Now, having the full data flow, how can I bind this property and still make the download complete?

I predict that further up in your call stack, you are calling Wait or Result on a Task returned from an async method. This can easily cause deadlock , as I describe on my blog.

This happens because await will by default capture a "context" which it uses to resume the async method. In your example, this is most likely a UI context, which always has exactly one thread (the UI thread). So if you block the UI thread by calling Wait or Result , then the async method cannot re-enter the UI context to complete its execution (and you end up waiting for a Task that cannot complete).

Edit:

Since you're databinding, I recommend using the NotifyTaskCompletion type here (which will soon be part of my AsyncEx library):

public MyViewModel()
{
    UpcomingMovies = NotifyTaskCompletion.Create(LoadUpcomingMoviesAsync());
}

public INotifyTaskCompletion<List<MovieViewModel>> UpcomingMovies { get; private set; }

private async Task<List<MovieViewModel>> LoadUpcomingMoviesAsync()
{
    System.Diagnostics.Debug.WriteLine("UpcomingMovies - Begin");             
    var apiMovies = await _serviceRT.FindUpcomingMoviesList();
    var movies = apiMovies.Select(result => new MovieViewModel(result)).ToList();
    System.Diagnostics.Debug.WriteLine("UpcomingMovies - End");
    return movies;
}

Then you can safely bind to INotifyTaskCompletion<T>.Result like this:

{Binding UpcomingMovies.Result}

Note that until you load the movies, the result will be null so your view will be empty. Also, if there is an error loading the movies, your view will always be empty. I recommend that you handle these situations by databinding to the other INotifyTaskCompletion<T> properties, eg:

  • {Binding UpcomingMovies.IsCompleted} , which will start out false and become true when the loading completes (either successfully or in error)
  • {Binding UpcomingMovies.IsSuccessfullyCompleted} , which becomes true only if/when the loading completes successfully
  • {Binding UpcomingMovies.IsFaulted} , which becomes true only if/when the loading completes with an error
  • {Binding UpcomingMovies.ErrorMessage} , which extracts the error message (and is null if there is no error)

HttpClient has limitations according to platform it is called from (like other network-related APIs on Windows Phone if compared with "big windows"). Maybe, this how-to will help: http://blogs.msdn.com/b/bclteam/archive/2013/02/18/portable-httpclient-for-net-framework-and-windows-phone.aspx

HttpClientHandler handler = new HttpClientHandler();
httpClient = new HttpClient(handler);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, resourceAddress);
request.Content = streamContent;
if (handler.SupportsTransferEncodingChunked())
{
    request.Headers.TransferEncodingChunked = true;
}
HttpResponseMessage response = await httpClient.SendAsync(request);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM