Calling async method from MVVM property - c#

I'm implementing my own version of auto-complete in a view model in my Xamarin Forms 5 app.
I need to call an async look up function from the Keyword MVVM property. Not sure if I'm handling it right. See below:
string keyword { get; set; }
ObservableRangeCollection<string> suggestions = new ObservableRangeCollection<string>();
public string Keyword
{
get => keyword;
set
{
if(keyword == value)
return;
keyword = value;
OnPropertyChanged();
// If keyword has at least 3 characters, get suggestions
if(keyword.length > 2)
GetSuggestions(keyword).Wait();
}
}
ObservableRangeCollection<string> Suggestions
{
get => suggestions;
set
{
if(sugggestions == value)
return;
suggestions = value;
OnPropertyChanged();
}
}
async Task GetSuggestions(string searchKeyword)
{
var result = await _myApiService.GetSuggestions(searchKeyword);
if(result != null)
{
Suggestions = new ObservableRangeCollection(result);
OnPropertyChanged(Suggestions);
}
}
I'd appreciate any corrections or suggestions. Thanks.

You definitely don't want to block on async code here (as explained on my blog). More generally, you don't want to block the UI, especially when your users are interacting with it (i.e., typing).
Instead, use a pattern like async data binding to (synchronously) start the operation, and then update your UI when the results come in.
E.g., using NotifyTask<T> from here:
string keyword { get; set; }
NotifyTask<List<string>> suggestions;
public string Keyword
{
get => keyword;
set
{
if (keyword == value)
return;
keyword = value;
OnPropertyChanged();
// If keyword has at least 3 characters, get suggestions
if (keyword.length > 2)
suggestions = NotifyTask.Create(GetSuggestionsAsync(keyword));
}
}
NotifyTask<List<string>> Suggestions
{
get => suggestions;
}
async Task<List<string>> GetSuggestionsAsync(string searchKeyword)
{
return await _myApiService.GetSuggestions(searchKeyword);
}
Then, instead of data-binding to Suggestions as a collection of string, data-bind to Suggestions.Result instead. You can also data-bind to Suggestions.IsCompleted (or Suggestions.IsNotCompleted) to show a loading/busy indicator, and Suggestions.IsFaulted to show an error message.

Related

Call method when ObservableProperty changes using CommunityToolkit.Mvvm

I'm implementing auto complete feature in my .NET MAUI app and I'm using CommunityToolkit.Mvvm code generators in my view model to handle observable properties.
I have the following code and I'm trying call GetSuggestions() method when the SearchText changes.
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(GetSuggestions))]
string searchText;
[ObservableProperty]
bool showSuggestions;
ObservableCollection<string> Suggestions { get; } = new();
private async Task GetSuggestions()
{
if(string.IsNullOrEmpty(SearchText) || SearchText.Length < 3)
return;
var data = await _myApiService.GetSuggestions(SearchText.Trim());
if(data != null && data.Count > 0)
{
Suggestions.Clear();
foreach(var item in data)
Suggestions.Add(item);
ShowSuggestions = true;
}
}
This is giving me the following error:
The target(s) of [NotifyCanExecuteChangedFor] must be an accessible
IRelayCommand property, but "GetSuggestions" has no matches in type
MyViewModel.
What am I doing wrong here?
I guess there are two problems here.
Why is this error occurring?
That happens because GetSuggestions is not a Command.
Try adding the [RelayCommand] attribute to your method.
[RelayCommand]
private async Task GetSuggestions()
{
if(string.IsNullOrEmpty(SearchText) || SearchText.Length < 3)
return;
var data = await _myApiService.GetSuggestions(SearchText.Trim());
if(data != null && data.Count > 0)
{
Suggestions.Clear();
foreach(var item in data)
Suggestions.Add(item);
ShowSuggestions = true;
}
}
Then link NotifyCanExecuteChangedFor to the autogenerated command.
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(GetSuggestionsCommand))]
string searchText;
The second one.
You need to
call GetSuggestions() method when the SearchText changes.
The NotifyCanExecuteChangedFor attribute doesn't do that.
In the autogenerated source code you should find an empty partial method called OnSearchTextPropertyChanged. Try implementing it.
partial void OnSearchTextPropertyChanged(string value)
{
GetSuggestions();
}
If this is what you're searching for, GetSuggestions doesn't need to be marked with the RelayCommand attribute.
Only meant as more of an amendment of #RMinato's answer.
As my comment say: "While most of this was helpful, I need to do a few things different including using the [RelayCommand] and calling the method inside my OnPropChanged method to be Task.Run(() => this.MyMethodAsync()).Wait();".
My code looks like:
[QueryProperty(nameof(Course), nameof(Course))]
public partial class CourseDetailViewModel : BaseViewModel
{
private readonly CourseService courseService;
public CourseDetailViewModel(CourseService courseService)
{
this.courseService = courseService;
}
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(GetCourseDetailCommand))]
Course course;
partial void OnCourseChanged(Course value)
{
Task.Run(() => this.GetCourseDetailAsync()).Wait();
}
[RelayCommand]
public async Task GetCourseDetailAsync()
{
if (GetCourseDetailCommand.IsRunning) return;
try
{
IsBusy = true;
course = await courseService.GetCourseDetailAsync(course.Id);
}
catch (Exception ex)
{
Debug.WriteLine($"Failed to get course detail. Error: {ex.Message}");
await Shell.Current.DisplayAlert("Error!",
$"Failed to get course detail: {ex.Message}", "OK");
throw;
}
finally
{
IsBusy = false;
}
}
}

WPF Thread dead lock [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
may i find useful answer from you.
I have WPF application fetching the data from API, I have thread deadlock when i fire following code
private List<Clinic> _clinics;
public List<Clinic> Clinics {
get { return _clinics; }
set { _clinics = value; OnPropertyChanged(); }
}
private Clinic _selectedClinic;
public Clinic SelectedClinic {
get { return _selectedClinic; }
set {
_selectedClinic = value; OnPropertyChanged();
if (value != null)
OnSelectClinic(value).Wait();
}
}
private async Task OnSelectClinic(Clinic clinic) {
try {
using (var request = await Client.GetAsync("URL OF API")) {
if (request.IsSuccessStatusCode){
StaffList = await request.Content.ReadAsAsync<Staff>();
}
var error = await request.Content.ReadAsStringAsync();
throw new Exception(error);
}
} catch (Exception e) {
Debug.WriteLine(e);
}
}
this is the view model, and Clinics, selectedClinic and Staff are binding to View.XAML
and here is the API Method
[HttpGet("FindAllByClinic/{clinicId}")]
public async Task<IActionResult> FindAllByClinic(int clinicId) {
try {
return Ok(_mapper.Map<List<Staff>>(await _staffRepository
.FindAllBy(clinicId)));
} catch (Exception e) {
return BadRequest(e.Message);
}
}
Here is StaffRepository method :
public async Task<ICollection<Staff>> FindAllBy(int clinicId, int medicalPointId) {
var persons = await Context.Persons.FromSql("EXEC HR.GetPersonsByClinicId #ClinicId = 0," +
$"#EncryptPassword = '{DecryptPassword}'").ToListAsync();
var staffs = await Context.Staff
.Include(e => e.StaffClinics)
.ThenInclude(e => e.Clinic)
.ThenInclude(e => e.ClinicMedicalPoints)
.Where(e => e.StaffClinics.Any(c => c.ClinicId == clinicId
&& c.Clinic.ClinicMedicalPoints.Any(m => m.MedicalPointId
== medicalPointId)))
.ToListAsync();
foreach (var staff in staffs) {
staff.Person = persons.FirstOrDefault(e => e.Id == staff.PersonId);
}
return staffs;
}
the problem is when i select a clinic from combobox the entire application freeze
Can any one give me suggestions or correct my code if I Have mistakes please
public Clinic SelectedClinic {
get { return _selectedClinic; }
set {
_selectedClinic = value; OnPropertyChanged();
if (value != null)
OnSelectClinic(value).Wait();
}
}
This setter is super bad, because:
It raises an event with OnPropertyChanged - that's okay - but then it continues to perform a costly operation in the setters body. You're already rasing an event, maybe something should subscribe to it and perform the OnSelectClinic instead?
You're blocking on async code, which is a sin that causes deadlocks. Normally the solution is to replace blocking waits with awaits, but in this case you need to redesign so that a property setter is not responsible for firing and awaiting this operation.

await / async task not waiting

I am experiencing some confusion with Tasks and the async/await key words. I understand that you should NOT mix async and blocking code. Or at least what my interpretation of mixing them is:
Don't make calls to blocking API's from non- async methods. So here's my issue.
I am trying to await a method, then update the UI accordingly. The issue is that the only way to await an async method() call is from within and async method().
Here's an example:
private RelayCommand<Options> _executeCommand;
public RelayCommand<Options> ExecuteCommand
{
get
{
return _executeCommand ?? (_executeCommand = new RelayCommand<Options>(async (options) =>
{
Completed = false;
var cancellationTokenSource = new CancellationTokenSource();
await RunValidation(options, cancellationTokenSource.Token);
Completed = true;
}));
}
}
This code runs the method properly and awaits. The issue is when I return. For some reason when setting the Complete flag the buttons dependent on this flag are not toggled. If I comment the await code, then the buttons are toggled correctly. So assumed I was not returning on the UI thread, so I tried using this code instead:
private RelayCommand<Options> _executeCommand;
public RelayCommand<Options> ExecuteCommand
{
get
{
return _executeCommand ?? (_executeCommand = new RelayCommand<Options>(async (options) =>
{
Completed = false;
var cancellationTokenSource = new CancellationTokenSource();
var context = TaskScheduler.FromCurrentSynchronizationContext();
await RunValidation(options, cancellationTokenSource.Token).ContinueWith(t => Completed = true, context);
//Completed = true;
}));
}
}
Here is the RunValidation() method:
private async Task RunValidation(Options options, CancellationToken token)
{
await _someService.SomAsyncMethod(options, token));
}
If you notice, the ExecuteCommand has an async key word before the (options) parameter that is passed to the command. If I remove the async key word then I have to modify the call to the RunValidation() method. I still need it to await, so this is what I did:
private RelayCommand<Options> _executeCommand;
public RelayCommand<Options> ExecuteCommand
{
get
{
return _executeCommand ?? (_executeCommand = new RelayCommand<Options>((options) =>
{
Completed = false;
var context = TaskScheduler.FromCurrentSynchronizationContext();
var cancellationTokenSource = new CancellationTokenSource();
Task.Run(async () => await RunValidation(options, cancellationTokenSource.Token));
Completed = true;
}));
}
}
The problem with this code is that it doesn't await. So I am at a loss.
Can anyone shed some light on this for me please. I've spend 2 plus days on this and I am still here.
Thanks,
Tim
Here are the bindings to the Command Buttons.
private readonly Independent<bool> _completed = new Independent<bool>(true);
public bool Completed
{
get { return _completed; }
set { _completed.Value = value; }
}
private ICommand _doneCommand;
public ICommand DoneCommand
{
get
{
return _doneCommand ?? (_doneCommand = MakeCommand.When(() => Completed).Do(() =>
{
DoSomething();
}));
}
}
private ICommand _cancelCommand;
public ICommand CancelCommand
{
get
{
return _cancelCommand ??
(_cancelCommand = MakeCommand.When(() => !Completed).Do(() => DoSomthingElse()));
}
}
I am using the MakeCommand objects from the UpdateControls library from Michael Perry. They contain dependancy tracking that raises the CanExecuteChange events when the Complete property is changed.
Your first code is correct. Most likely you have an incorrect implementation for your Completed property. Your view model object should implement INotifyPropertyChanged. The easiest way to do this right is use a base class that provides the functionality. ReactiveUI is the nuget package I always use. Usage is as simple as
public class MyObject : ReactiveObject {
private bool _Completed;
public bool Completed {
get => _Completed;
set => this.RaiseAndSetIfChanged(ref _Completed, value);
}
}
This will make sure that notifications are raised to the UI when the property is changed.
If you want it to be more magic you can use ReactiveUI.Fody and then your code will reduce to
public class MyObject : ReactiveObject {
[Reactive]
public bool Completed { get; set;}
}
So the issue was in fact the third party library. I was using it to provide dependency tracking for the 2 buttons. So when the complete flag changed it raised the CanExecuteChange events for both buttons without me having write code to do it. Unfortunately it stopped working after introducing the async/await calls. I replaced the 2 MakeCommands with RelayCommands and raised the events myself and everything worked.
So thanks to everyone for your responses.
Tim

Caliburn.Micro not updating UI

I'm trying to build simple utility with caliburn micro and wpf. This simple code:
public bool CanTransfer
{
get { return this.canTransfer; }
set
{
this.NotifyOfPropertyChange(() => this.CanTransfer);
this.canTransfer = value;
}
}
public void Transfer()
{
Task.Factory.StartNew(this.Process);
}
private void Process()
{
this.CanTransfer = false;
Thread.Sleep(5000);
this.CanTransfer = true;
}
Does the following - Transfer button remains disabled, but I'd expect it to be enabled 5 seconds after, what am I doing wrong?
Swap those lines in CanTransfer setter method - now you first say something has changed while it actually hasn't and then you change it:
public bool CanTransfer
{
get { return this.canTransfer; }
set
{
this.canTransfer = value;
this.NotifyOfPropertyChange(() => this.CanTransfer);
}
}
I usually use method like ChangeAndNotify<T> (the only annoyance is declaration of the field), the code can be seen here:
private bool _canTransfer;
public string CanTransfer
{
get { return _canTransfer; }
set
{
PropertyChanged.ChangeAndNotify(ref _canTransfer, value, () => CanTransfer);
}
}
You are missing a notifiation:
this.NotifyOfPropertyChange(() => this.CanTransfer);
I hope I got the syntax right, I am typing from memory.
This basically tells WPF "Hey something changed, go and look at the new value".

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

What'd be the most elegant way to call an async method from a getter or setter in C#?
Here's some pseudo-code to help explain myself.
async Task<IEnumerable> MyAsyncMethod()
{
return await DoSomethingAsync();
}
public IEnumerable MyList
{
get
{
//call MyAsyncMethod() here
}
}
There is no technical reason that async properties are not allowed in C#. It was a purposeful design decision, because "asynchronous properties" is an oxymoron.
Properties should return current values; they should not be kicking off background operations.
Usually, when someone wants an "asynchronous property", what they really want is one of these:
An asynchronous method that returns a value. In this case, change the property to an async method.
A value that can be used in data-binding but must be calculated/retrieved asynchronously. In this case, either use an async factory method for the containing object or use an async InitAsync() method. The data-bound value will be default(T) until the value is calculated/retrieved.
A value that is expensive to create, but should be cached for future use. In this case, use AsyncLazy from my blog or AsyncEx library. This will give you an awaitable property.
Update: I cover asynchronous properties in one of my recent "async OOP" blog posts.
You can't call it asynchronously, since there is no asynchronous property support, only async methods. As such, there are two options, both taking advantage of the fact that asynchronous methods in the CTP are really just a method that returns Task<T> or Task:
// Make the property return a Task<T>
public Task<IEnumerable> MyList
{
get
{
// Just call the method
return MyAsyncMethod();
}
}
Or:
// Make the property blocking
public IEnumerable MyList
{
get
{
// Block via .Result
return MyAsyncMethod().Result;
}
}
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");
}
}
}
You can use Task like this :
public int SelectedTab
{
get => selected_tab;
set
{
selected_tab = value;
new Task(async () =>
{
await newTab.ScaleTo(0.8);
}).Start();
}
}
I think that we can await for the value just returning first null and then get the real value, so in the case of Pure MVVM (PCL project for instance) I think the following is the most elegant solution:
private IEnumerable myList;
public IEnumerable MyList
{
get
{
if(myList == null)
InitializeMyList();
return myList;
}
set
{
myList = value;
NotifyPropertyChanged();
}
}
private async void InitializeMyList()
{
MyList = await AzureService.GetMyList();
}
I thought .GetAwaiter().GetResult() was exactly the solution to this problem, no?
eg:
string _Title;
public string Title
{
get
{
if (_Title == null)
{
_Title = getTitle().GetAwaiter().GetResult();
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
Since your "async property" is in a viewmodel, you could use AsyncMVVM:
class MyViewModel : AsyncBindableBase
{
public string Title
{
get
{
return Property.Get(GetTitleAsync);
}
}
private async Task<string> GetTitleAsync()
{
//...
}
}
It will take care of the synchronization context and property change notification for you.
You can create an event and invoke an event when the property is changed.
Something like this:
private event EventHandler<string> AddressChanged;
public YourClassConstructor(){
AddressChanged += GoogleAddressesViewModel_AddressChanged;
}
private async void GoogleAddressesViewModel_AddressChanged(object sender, string e){
... make your async call
}
private string _addressToSearch;
public string AddressToSearch
{
get { return _addressToSearch; }
set
{
_addressToSearch = value;
AddressChanged.Invoke(this, AddressToSearch);
}
}
When I ran into this problem, trying to run an async method synchronicity from either a setter or a constructor got me into a deadlock on the UI thread, and using an event handler required too many changes in the general design.
The solution was, as often is, to just write explicitly what I wanted to happen implicitly, which was to have another thread handle the operation and to get the main thread to wait for it to finish:
string someValue=null;
var t = new Thread(() =>someValue = SomeAsyncMethod().Result);
t.Start();
t.Join();
You could argue that I abuse the framework, but it works.
Necromancing.
In .NET Core/NetStandard2, you can use Nito.AsyncEx.AsyncContext.Run instead of System.Windows.Threading.Dispatcher.InvokeAsync:
class AsyncPropertyTest
{
private static async System.Threading.Tasks.Task<int> GetInt(string text)
{
await System.Threading.Tasks.Task.Delay(2000);
System.Threading.Thread.Sleep(2000);
return int.Parse(text);
}
public static int MyProperty
{
get
{
int x = 0;
// https://stackoverflow.com/questions/6602244/how-to-call-an-async-method-from-a-getter-or-setter
// https://stackoverflow.com/questions/41748335/net-dispatcher-for-net-core
// https://github.com/StephenCleary/AsyncEx
Nito.AsyncEx.AsyncContext.Run(async delegate ()
{
x = await GetInt("123");
});
return x;
}
}
public static void Test()
{
System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
System.Console.WriteLine(MyProperty);
System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
}
}
If you simply chose System.Threading.Tasks.Task.Run or System.Threading.Tasks.Task<int>.Run, then it wouldn't work.
I think my example below may follow #Stephen-Cleary 's approach but I wanted to give a coded example. This is for use in a data binding context for example Xamarin.
The constructor of the class - or indeed the setter of another property on which it is dependent - may call an async void that will populate the property on completion of the task without the need for an await or block. When it finally gets a value it will update your UI via the NotifyPropertyChanged mechanism.
I'm not certain about any side effects of calling a aysnc void from a constructor. Perhaps a commenter will elaborate on error handling etc.
class MainPageViewModel : INotifyPropertyChanged
{
IEnumerable myList;
public event PropertyChangedEventHandler PropertyChanged;
public MainPageViewModel()
{
MyAsyncMethod()
}
public IEnumerable MyList
{
set
{
if (myList != value)
{
myList = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
}
}
}
get
{
return myList;
}
}
async void MyAsyncMethod()
{
MyList = await DoSomethingAsync();
}
}
I review all answer but all have a performance issue.
for example in :
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");
}
}
}
Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
use dispatcher which is not a good answer.
but there is a simple solution, just do it:
string _Title;
public string Title
{
get
{
if (_Title == null)
{
Task.Run(()=>
{
_Title = getTitle();
RaisePropertyChanged("Title");
});
return;
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
You can change the proerty to Task<IEnumerable>
and do something like:
get
{
Task<IEnumerable>.Run(async()=>{
return await getMyList();
});
}
and use it like
await MyList;

Categories

Resources