1

Let's suppose I have a long running Web API call (async method) which returns a string.

Is there a best practice between those 2 solutions to display the result in a WPF property without blocking UI ? Or is there another one ?

Note: both solutions are not freezing UI and I already view posts How to call an async method from a getter or setter? and Async property in c#.

My Wep API

private async Task<string> GetAsyncProperty()
{
    string result = "Async Property Value";

    // Web api call...
    await Task.Delay(TimeSpan.FromSeconds(10));

    return result;
}

Solution A

XAML:

<TextBlock Text="{Binding Path=AsyncPropertyA, UpdateSourceTrigger=PropertyChanged}" />

ViewModel:

public MyConstructor()
{
    Task task = SetAsyncPropertyA();
}

private async Task SetAsyncPropertyA()
{
    this.AsyncPropertyA = await GetAsyncProperty().ConfigureAwait(false);
}

Solution B

XAML:

<TextBlock Text="{Binding Path=AsyncPropertyB, UpdateSourceTrigger=PropertyChanged, IsAsync=True, FallbackValue='Loading B...'}" />

ViewModel:

public string AsyncPropertyB
{
    get
    {
        return GetAsyncPropertyB();
    }
}

private string GetAsyncPropertyB()
{
    return Task.Run(() => GetAsyncProperty()).Result;
}

Note: in solution B, I can add FallbackValue that's not working in solution A and potentially some other UI updates in ContinueWith of the Task.Run.

jootl
  • 699
  • 2
  • 8
  • 16
  • 3
    Note that setting `UpdateSourceTrigger=PropertyChanged` on the Bindings is pointless. It only has an effect in Bindings that actually update their source property, i.e. TwoWay or OneWayToSource Bindings. – Clemens Oct 05 '17 at 11:49
  • In B, it should be sufficient to return `GetAsyncProperty().Result` from the property getter. – Clemens Oct 05 '17 at 12:02
  • Thanks! And if I want to display a spinner bound to a boolean to know when the update is done, can I do it without `Task.Run(...).ContinueWith(x => IsLoading = false...`? – jootl Oct 05 '17 at 12:08
  • 3
    .Result is blocking and can lead to deadlocks in WPF/UI environments. Don't use .Result or .Wait! – Peter Bons Oct 05 '17 at 12:25
  • @PeterBons It's not if I use `IsAsync=True` in XAML. Is there any chance that can lead to deadlocks in another moment even if I used `IsAsync` ? – jootl Oct 05 '17 at 12:35
  • @BradleyUffner Like I said in my post and my previous comment, if I add `IsAsync=True`, it's not blocking UI but I agree if there isn't `IsAsync` it's blocking UI. – jootl Oct 05 '17 at 14:12

2 Answers2

3

You should try to use a good framework for that, which already inveted that wheel.

Take a look at ReactiveUI command sample:

LoadTweetsCommand = ReactiveCommand.CreateAsyncTask(() => LoadTweets())

LoadTweetsCommand.ToProperty(this, x => x.TheTweets, out theTweets);
LoadTweetsCommand.ThrownExceptions.Subscribe(ex => /* handle exception here */);

Those extensions work on IObservable, which on itself is very powerfull tool:

Observable.FromAsync(async () =>
            {
                await Task.Delay(100);
                return 5;
            }).ToProperty(x => )
Krzysztof Skowronek
  • 2,796
  • 1
  • 13
  • 29
  • Thanks! I didn't know this framework, I will take a look for my personal use. But my company and me don't trust everyone on the Internet even it's a powerfull framework from your words... At the moment there is only 3100 stars on GitHub and I don't know if in 5 years there will be support and updates. – jootl Oct 05 '17 at 12:16
  • Trust? It is open source. It does it job at the moment very well so where are you afraid of? Please don't try to reinvent the wheel. It will cost you more and will probably less stable. – Peter Bons Oct 05 '17 at 12:22
  • 3
    Then focus on observables themselves, they are part of .NET. Reactive programming is very neat :) – Krzysztof Skowronek Oct 05 '17 at 12:55
  • Five years later... its up to 7.6 starts! But gawsh I still don't know. If it goes unsupported in another 5 years, what will I do?! Never know with those OSS projects... I guess I could just maintaining it myself if I still need it. – BenCamps Aug 18 '23 at 18:18
3

In both cases, you're not catching any errors that might happen while trying to call the Web API. You might want to log it to a file and/or show an error message to the user.

In that case, await makes it easy - you can just use try/catch:

public MyConstructor()
{
    try
    {
        Task task = SetAsyncPropertyA();
    }
    catch (Exception e)
    {
        // log the error or show a message
    }
}

private async Task SetAsyncPropertyA()
{
    this.AsyncPropertyA = await GetAsyncProperty().ConfigureAwait(false);
}

You could also move the try/catch to the async method. In that case, since there's no chance of an error escaping from it, you could make it async void. Sometimes this is necessary for event handlers (at least in Windows Forms - not sure about WPF.)

public MyConstructor()
{
    SetAsyncPropertyA();
}

private async void SetAsyncPropertyA()
{
    try
    {
        this.AsyncPropertyA = await GetAsyncProperty().ConfigureAwait(false);
    }
    catch (Exception e)
    {
        // log the error or show a message
    }
}
libertyernie
  • 2,590
  • 1
  • 17
  • 13