How to Call an Async Method from a Getter or Setter

How to call an async method from a getter or setter?

I really needed the call to originate from the get method, due to my decoupled architecture. So I came up with the following implementation.

Usage: Title is in a ViewModel or an object you could statically declare as a page resource. Bind to it and the value will get populated without blocking the UI, when getTitle() returns.

string _Title;
public string Title
{
get
{
if (_Title == null)
{
Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}

How to call an async method from a property setter

I will assume that DisplayValues method implementation is changing a property that is bound to the UI and for the demonstration I will assume it's a List<string>:

private List<string> _values;

public List<string> Values
{
get
{
return _values;
}
private set
{
_values = value;
OnPropertyChange();
}
}

And it's bindings:

<ListBox ItemsSource="{Binding Values}"/>

Now as you said it is not allowed to make property setters async so we will have to make it sync, what we can do instead is to change Values property to some type that will hide the fact it's data comming from asynchronous method as an implementation detail and construct this type in a sync way.

NotifyTask from Stephen Cleary's Mvvm.Async library will help us with that, what we will do is change Values property to:

private NotifyTask<List<string>> _notifyValuesTask;

public NotifyTask<List<string>> NotifyValuesTask
{
get
{
return _notifyValuesTask;
}
private set
{
_notifyValuesTask = value;
OnPropertyChange();
}
}

And change it's binding:

<!-- Busy indicator -->
<Label Content="Loading values" Visibility="{Binding notifyValuesTask.IsNotCompleted,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<!-- Values -->
<ListBox ItemsSource="{Binding NotifyValuesTask.Result}" Visibility="{Binding
NotifyValuesTask.IsSuccessfullyCompleted,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<!-- Exception details -->
<Label Content="{Binding NotifyValuesTask.ErrorMessage}"
Visibility="{Binding NotifyValuesTask.IsFaulted,
Converter={StaticResource BooleanToVisibilityConverter}}"/>

This way we created a property that represents a Task alike type that is customized for databinding, including both busy indicator and errors propagation, more info about NotifyTask usage in this MSDN articale (notice that NotifyTask is consider there as NotifyTaskCompletion).

Now the last part is to change Filter property setter to set notifyValuesTask to a new NotifyTask every time the filter is changed, with the relevant async operation (no need to await anything, all the monitoring is already embedded in NotifyTask):

private string _filter;

public string Filter
{
get
{
return _filter;
}
set
{
_filter = value;
// Construct new NotifyTask object that will monitor the async task completion
NotifyValuesTask = NotifyTask.Create(GetValuesFromWebApi(_filter));
OnPropertyChange();
}
}

You should also notice that GetValuesFromWebApi method blocks and it will make your UI freeze, you shouldn't use Result property after calling GetAsync use await twice instead:

public async Task<string> GetValuesFromWebApi(string query)
{
var url = $"http://localhost:57157/api/v1/test/result/{query}";
using(var response = await _httpClient.GetAsync(url))
{
return await response.Content.ReadAsStringAsync();
}
}

using await inside setters

Simple answer: You can't. All properties (both get and set) and synchronous. Keep it an async function and you're good.

Complex answer: You can, but it's ugly. Make the async function a private one, and call it from your set method. But since the set is synchronous, while the calling method is async , you'll have to make this call differently. Look at this SO post for various options.

This note this last option should only be used when you don't have any other option. Debugging might be hard and you might run into race conditions you don't want to happen.



Related Topics



Leave a reply



Submit