Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a well-defined pattern for binding controls to asynchronous data?

Can you tell whether the following snippets are correctly binding to an asynchronous data source ?

While it seems to be working, i.e : UI does not freeze, I'm not entirely sure about the correctness as the MSDN documentation does not really talk about binding to 'async' methods in these docs :

Binding.IsAsync

ObjectDataProvider.IsAsynchronous

<pages:HottestPageProxy x:Key="PageProxy" ></pages:HottestPageProxy>

<ObjectDataProvider x:Key="DataProviderArtists" IsAsynchronous="True"  ObjectInstance="{StaticResource PageProxy}"  MethodName="GetArtists">
    <ObjectDataProvider.MethodParameters>
        <system:String>Grabbing artists !</system:String>
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

(HottestPageProxy object is a small helper that provides data for the controls)

public class HottestPageProxy
{
    [UsedImplicitly]
    public async Task<ArtistsQuery> GetArtists([CallerMemberName] string memberName = "")
    {
        Console.WriteLine(memberName);
        string apiKey = App.GetApiKey();
        Task<ArtistsQuery> topHottt = Queries.ArtistTopHottt(new ArtistTopHotttParameters
        {
            ApiKey = apiKey,
            Results = 100,
            Buckets = new[] {ArtistTopHotttBuckets.Hotttnesss}
        });
        return (await topHottt);
    }
}

EDIT : method that 'await topHottt' calls

public static async Task<ArtistsQuery> ArtistTopHottt(ArtistTopHotttParameters parameters)
{
    if (parameters == null) throw new ArgumentNullException("parameters");
    return await Get<ArtistsQuery>(parameters);
}

private static async Task<T> Get<T>(Parameters parameters) where T : Query
{
    if (parameters == null) throw new ArgumentNullException("parameters");
    ValidateParameters(parameters);

    string url = parameters.GetQueryUrl();
    var value = new Uri(url);
    using (var client = GetHttpClient())
    using (var message = await client.GetAsync(url))
    {
        // fetch message content (removed)
        return GetQueryResultObject<T>(s);
    }
}

private static T GetQueryResultObject<T>(string json) where T : class
{
    // create T from Json string (removed)
    return t;
}

EDIT using AsyncEx

Using your library works though the syntax now is :

<ItemsControl x:Name="ItemsControlTopHott"
    ItemsSource="{Binding ... Path=Artists.Result.Artists ...}">
</ItemsControl>

Is 'Artists.Result.Artists' really what you expect me to use ? :)

The new syntax makes it more confusing as the source is :

public sealed class ArtistsQuery : Query
{
    public List<Artist> Artists { get; set; }
}

Not a big deal but if I could avoid such syntax that'd be great.

You said that .Result might bring a deadlock, did I miss something in implementing your solution then ?

Artists.PropertyChanged event raised the following messages :

  • Status: RanToCompletion
  • IsCompleted: True
  • IsSuccessfullyCompleted: True
  • Result: Success

I'll give a try to .ConfigureAwait(false) as you've mentioned in your post to see how it goes with it.

Forgot to mention, actually my implementation using .Result indeed does not block the UI as the typical time to get the result is a few seconds; I'd have seen the UI freeze. It seems right ... but I'm not certain whether it's correct, hence my question.

like image 466
aybe Avatar asked Sep 10 '13 03:09

aybe


1 Answers

As others have noted, the "asynchronous" members in WPF types have nothing to do with async and await.

You do have a problem in your binding; your path is using Task.Result, which will block the UI thread until the query is complete. Also, using Result brings up the possibility of a deadlock that I describe on my blog.

I have another blog entry that deals with async properties, in particular how to data-bind to properties that are (logically) asynchronous. My AsyncEx library has a type called NotifyTaskCompletion that allows you to data-bind more naturally to an asynchronous Task.

So, e.g., you could do something like this:

public class HottestPageProxy
{
  public HottestPageProxy()
  {
    Artists = NotifyTaskCompletion.Create(GetArtists());
  }

  public INotifyTaskCompletion<ArtistsQuery> Artists { get; private set; }

  private Task<ArtistsQuery> GetArtists()
  {
    string apiKey = App.GetApiKey();
    return Queries.ArtistTopHottt(new ArtistTopHotttParameters
    {
        ApiKey = apiKey,
        Results = 100,
        Buckets = new[] {ArtistTopHotttBuckets.Hotttnesss}
    });
  }
}

Then you can data-bind to several different properties on INotifyTaskCompletion<T>, including IsCompleted, ErrorMessage, and Result (which does not block; it will return default(T) if the task is not completed).

like image 137
Stephen Cleary Avatar answered Nov 02 '22 23:11

Stephen Cleary