How to update UI after async Task in viewmodel - c#

In my Xamarin.Forms pcl project, I have a xaml page with label. I want to update label after some async task. In my ViewModel constructor I set default text for my label. And create a async Task function named SomeTask().
Question 1: Where can I call SomeTask() function. Not able to call async Task function in constructor.
Question 2: How to update Label text after async Task SomeTask() function.
My code:
public class MyPageViewModel : ViewModelBase
{
private String _selectedText;
public String SelectedText
{
get { return _selectedText; }
set {
if (_selectedText != value)
{
_selectedText = value;
}
}
}
public MyPageViewModel ()
{
_selectedText = "Welcome"; //Default text
}
private async Task<string> SomeTask()
{
return await Task.Run(async () =>
{
await Task.Delay(3000); //Dummy task. It will return the status of Task.
return "Thanks"; //Update Text
});
}
}

I recommend that you use my NotifyTask type; it's described in my MSDN article on asynchronous MVVM data binding, and I think it's the easiest approach:
public class MyPageViewModel : ViewModelBase
{
private NotifyTask<string> _selectedText;
public NotifyTask<string> SelectedText => _selectedText;
public MyPageViewModel()
{
_selectedText = NotifyTask.Create(SomeTask(), "Welcome");
}
private async Task<string> SomeTask()
{
await Task.Delay(3000);
return "Thanks";
}
}
Your data binding would then be changed to bind to SelectedText.Result to display "Welcome" followed by "Thanks". There are other NotifyTask<T> properties for data binding, such as IsNotCompleted, IsCompleted, and ErrorMessage, which allows you to handle fault conditions via data binding as well.
If you don't want to use this type, you can do something similar on your own:
public class MyPageViewModel : ViewModelBase
{
private string _selectedText;
public string SelectedText
{
get { return _selectedText; }
set
{
if (_selectedText != value)
{
_selectedText = value;
RaisePropertyNotifyChanged(); // However you're doing this.
}
}
}
public MyPageViewModel()
{
_selectedText = "Welcome";
var _ = RunSomeTask();
}
private async Task RunSomeTask()
{
try
{
SelectedText = await SomeTask();
}
catch (Exception ex)
{
// TODO: Handle the exception.
// It *must* be handled here, or else it will be silently ignored!
}
}
private async Task<string> SomeTask()
{
await Task.Delay(3000);
return "Thanks";
}
}
The constructor starts a RunSomeTask operation and then explicitly ignores its results (note that this means all exceptions will be ignored). The RunSomeTask is responsible for running SomeTask and handling its results (and exceptions). The result is just used to update SelectedText, and exceptions will be handled however you deem appropriate for your app.

How about
public MyPageViewModel()
{
_selectedText = "Welcome"; //Default text
SomeTask().ContinueWith(previousTask => SelectedText = previousTask.Result);
}

You can create an async Factory Method and make you constructor private. Then you call that method to create an instance of MyPageViewModel. Inside that method, you can call string str = await SomeTask
public class MyPageViewModel : ViewModelBase
{
public async MyPageViewModel CreateAsync()
{
var model = new MyPageViewModel();
SelectedText = await SomeTask();
return model;
}
private MyPageViewModel ()
{
_selectedText = "Welcome"; //Default text
}
private Task<string> SomeTask()
{
return Task.Run(async () =>
{
await Task.Delay(3000); //Dummy task. It will return the status of Task.
return "Thanks"; //Update Text
});
}
}
So instead of creating your model like this:
var model = new MyPageViewModel();
You create it like this:
var model = await MyPageViewModel.CreateAsync();

Problem 1:
Use delegate and event.
Create Delegate & associated event:
private delegate void MyDelegate();
private event MyDelegate myEvent;
Subscribe to the event in constructor:
myEvent += async () => await SomeTask();
Execute the event, where ever you need:
myEvent(); //Note: Check the event for null, before executing
Problem 2:
If on a non-UI thread, then:
Use some framework class for executing UI operation: for example - Xamarin provides Device.BeginInvokeOnMainThread
We may always use DataBinding with the Label and only update the value of Binding Path from ViewModel, using event subscription.
string _message;
public string Message
{
get => _message;
set
{
_message = value;
}
}
myEvent += () => Message = "New Value";
<Label Text = "{Binding Message}"/>

Related

How can I check for a duplicate object in SQLite table in C#?

I am creating an application in Xamarin.Forms - it is an event management application - in the application the user can sign up to events and create events. When you create an event - you are given a code for that event. There is a window in which you can input the code and then it registers you to that event. So for example if someone wants to check who is coming to a birthday party they could put the code on the invitation and the people who receive the invitation could open the app - input the code - and say whether they are going or not and add some messages.
I followed this tutorial to implement SQLite into Xamarin.Forms:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/databases
Here is the code:
public class LocalEventDatabase : ILocalEventDatabase
{
static readonly Lazy<SQLiteAsyncConnection> lazyInitializer = new Lazy<SQLiteAsyncConnection>(() =>
{
return new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
});
static SQLiteAsyncConnection Database => lazyInitializer.Value;
static bool initialized = false;
public LocalEventDatabase()
{
InitializeAsync().SafeFireAndForget(false, new Action<Exception>(async(Exception ex) =>
{
await Acr.UserDialogs.UserDialogs.Instance.AlertAsync(string.Format($"Don't panic! An exception has occurred: {ex.Message}"), "An exception has occurred", "OK");
}));
}
async Task InitializeAsync()
{
if (!initialized)
{
if (!Database.TableMappings.Any(m => m.MappedType.Name == typeof(Event).Name))
{
await Database.CreateTablesAsync(CreateFlags.None, typeof(Event)).ConfigureAwait(false); // not entirely understood what configure await is?
}
initialized = true;
}
}
public async Task<List<Event>> GetRegisteredEventsAsync()
{
return await Database.Table<Event>().ToListAsync();
}
public async Task<Event> GetEventAsync(Guid id)
{
return await Database.Table<Event>().Where(i => i.EventGuid == id).FirstOrDefaultAsync();
}
public async Task<int> InsertEventAsync(Event #event)
{
return await Database.InsertAsync(#event);
}
public async Task<int> DeleteEventAsync(Event #event)
{
return await Database.DeleteAsync(#event);
}
}
Here is the code behind for searching through the codes:
public class FindEventViewModel : ViewModelBase
{
private ObservableCollection<Event> _results;
private ObservableCollection<Event> _events;
public ICommand SearchCommand => new Command(OnSearchCommand);
public string EntryText { get; set; }
// FAB stands = 'floating action button'
public ObservableCollection<Event> Results
{
get => _results;
set
{
_results = value;
RaisePropertyChanged(nameof(Results));
}
}
public ObservableCollection<Event> Events
{
get => _events;
set
{
_events = value;
RaisePropertyChanged(nameof(Events));
}
}
public FindEventViewModel(IDialogService dialogService,
IEventService eventService,
IShellNavigationService shellNavigationService,
IThemeService themeService,
ILocalEventDatabase localEventDatabase) : base(dialogService, eventService, shellNavigationService, themeService, localEventDatabase)
{
}
public async Task<FindEventViewModel> OnAppearing()
{
await LoadData();
return this;
}
private async Task LoadData()
{
var data = await _eventService.GetAllEventsAsync();
Events = new ObservableCollection<Event>(data);
Results = new ObservableCollection<Event>();
}
public async void OnSearchCommand()
{
if (SearchEvents(EntryText).SearchResult == SearchResult.Match)
{
_dialogService.ShowAdvancedToast(string.Format($"Found matching event"), Color.Green, Color.White, TimeSpan.FromSeconds(2));
var eventFound = SearchEvents(EntryText).EventFound;
Results.Add(eventFound);
RaisePropertyChanged(nameof(eventFound));
// may be unneccessary to use it directly rather than with DI but in this case it is needed in order to evaluate what the user has pressed
var result = await UserDialogs.Instance.ConfirmAsync(new Acr.UserDialogs.ConfirmConfig()
{
Title = "Sign up?",
Message = string.Format($"Would you like to sign up to {eventFound.EventTitle}?"),
OkText = "Yes",
CancelText = "No",
});
if (result)
{
await _localEventDatabase.InsertEventAsync(eventFound);
_dialogService.ShowAdvancedToast(string.Format($"You signed up to {eventFound.EventTitle} successfully"), Color.CornflowerBlue, Color.White, TimeSpan.FromSeconds(2));
MessagingCenter.Send(eventFound as Event, MessagingCenterMessages.EVENT_SIGNEDUP);
}
}
else
{
_dialogService.ShowAdvancedToast(string.Format($"No event found matching query - retry?"), Color.Red, Color.White, TimeSpan.FromSeconds(2));
}
}
public EventSearchResult SearchEvents(string code)
{
foreach (Event _event in Events)
{
if (_event.EventCode == code)
{
return new EventSearchResult()
{
SearchResult = SearchResult.Match,
EventFound = _event,
};
}
}
return new EventSearchResult()
{
SearchResult = SearchResult.NoMatch,
};
}
}
The method returns a search result and the event found. One problem - the user can sign up to the same event twice. In the tutorial I followed it doesn't mention how to check for any duplicate items in the database table?

uwp - 'The application called an interface that was marshalled for a different thread

I'm trying to create UWP chat using MVVM. I keep getting this error:
The application called an interface that was marshalled for a different thread
in my view model in here:
public void Notify(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
this function occurs every time a value is set to the string property that bond to the TextBlock.Text property in the xaml;
i tried inserting the TextBlock (that the vm was bond to its text property) in the view model and remove all the binding in the xaml and use this function:
private async Task LoadMessage(string sender, string message)
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
ChatBox += $"{sender}: {message} \n";
});
}
still the same, only now the exception is thrown in the above function.
I found anther dispatcher while searching for answer but it seems that uwp doesn't recognize it and mark it with red under-line:
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
//my code
});
I tried :
await CoreDispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
ChatBox += $"{sender}: {message} \n";
});
but then i got
an object reference is required for the non-static field, method
so I created a static class the holds this function :
public static void UpdateTextBlock(TextBlock textBlock, string sender, string message)
{
textBlock.Text = $"{sender}: {message}";
}
and inserting it to this dispatcher. still no-go. still: an object reference is required for...
i really want it to be MVVM, but any solution that works would be a bless.
EDIT
today i tried moving back to the binding and mvvm pattern. i was wrapping the LoadMessage function in task like this:
private async Task<bool> LoadMessage(string sender, string message)
{
bool flag = false;
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
TextBindingProperty += $"{sender}: {message} \n";
flag = true;
});
return flag;
}
_hubProxy.On<string, string>("SendMessage", async (sender, message) =>
{
var result = await Task.Run(() => LoadMessage(sender, message));
});
same exception only in class : MyView.g.cs
in this method:
public static void Set_Windows_UI_Xaml_Controls_TextBlock_Text(global::Windows.UI.Xaml.Controls.TextBlock obj, global::System.String value, string targetNullValue)
{
if (value == null && targetNullValue != null)
{
value = targetNullValue;
}
obj.Text = value ?? global::System.String.Empty; //in this line!!!
}
};
My view model implement INotifyPropertyChanged
public class UserViewModel : INotifyPropertyChanged
{
private string chatBox;
public string ChatBox
{
get { return chatBox; }
set { chatBox = value; Notify(nameof(ChatBox)); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void Notify(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
the xaml element that is bond to this property looks like this:
<TextBlock Name="chatTbl" Text="{x:Bind userViewModel.ChatBox, Mode=TwoWay}" />
Solution:
so battling to make the chat work this was the problem:
await CoreApplication.MainView
since the chat is in a different view than the main page it couldn't find the binding or the xaml element.
what my friend realize that in order to make changes in other view you need to get its object so, we added this property to the view model:
public CoreApplicationView CurrentView { get; set; }
when launching the chat room view we set the property:
public UserViewModel userViewModel;
public ChatRoom(UserViewModel userViewModel)
{
this.InitializeComponent();
this.userViewModel = userViewModel;
userViewModel.ChatTbl = chatTbl;
userViewModel.CurrentView = CoreApplication.GetCurrentView();//this line
}
now when we need to make changes in the view we use:
_hubProxy.On<string, string>("SendMessage", async (sender, message) =>
{
await CurrentView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
ChatTbl.Text += $"{sender}: {message} \n";
});
});
so instead await CoreApplication.MainView
use await CurrentView.CoreWindow
hope this would help someone i know it would have helped me
Your LoadMessage is an asynchronous method, you did not need to put it in Task.Run. You could directly call it like the following:
_hubProxy.On<string, string>("SendMessage", async (sender, message) =>
{
var result = await LoadMessage(sender, message);
});
If you have to put it in Task.Run, you could call it like this:
_hubProxy.On<string, string>("SendMessage", async (sender, message) =>
{
var result = await Task.Run(async() => await LoadMessage(sender, message));
});

Close task before run again

I working on real-time search. At this moment on property setter which is bounded to edit text, I call a method which calls API and then fills the list with the result it looks like this:
private string searchPhrase;
public string SearchPhrase
{
get => searchPhrase;
set
{
SetProperty(ref searchPhrase, value);
RunOnMainThread(SearchResult.Clear);
isAllFriends = false;
currentPage = 0;
RunInAsync(LoadData);
}
}
private async Task LoadData()
{
var response = await connectionRepository.GetConnections(currentPage,
pageSize, searchPhrase);
foreach (UserConnection uc in response)
{
if (uc.Type != UserConnection.TypeEnum.Awaiting)
{
RunOnMainThread(() =>
SearchResult.Add(new ConnectionUser(uc)));
}
}
}
But this way is totally useless because of it totally mashup list of a result if a text is entering quickly. So to prevent this I want to run this method async in a property but if a property is changed again I want to kill the previous Task and star it again. How can I achieve this?
Some informations from this thread:
create a CancellationTokenSource
var ctc = new CancellationTokenSource();
create a method doing the async work
private static Task ExecuteLongCancellableMethod(CancellationToken token)
{
return Task.Run(() =>
{
token.ThrowIfCancellationRequested();
// more code here
// check again if this task is canceled
token.ThrowIfCancellationRequested();
// more code
}
}
It is important to have this checks for cancel in the code.
Execute the function:
var cancellable = ExecuteLongCancellableMethod(ctc.Token);
To stop the long running execution use
ctc.Cancel();
For further details please consult the linked thread.
This question can be answered in many different ways. However IMO I would look at creating a class that
Delays itself automatically for X (ms) before performing the seach
Has the ability to be cancelled at any time as the search request changes.
Realistically this will change your code design, and should encapsulate the logic for both 1 & 2 in a separate class.
My initial thoughts are (and none of this is tested and mostly pseudo code).
class ConnectionSearch
{
public ConnectionSearch(string phrase, Action<object> addAction)
{
_searchPhrase = phrase;
_addAction = addAction;
_cancelSource = new CancellationTokenSource();
}
readonly string _searchPhrase = null;
readonly Action<object> _addAction;
readonly CancellationTokenSource _cancelSource;
public void Cancel()
{
_cancelSource?.Cancel();
}
public async void PerformSearch()
{
await Task.Delay(300); //await 300ms between keystrokes
if (_cancelSource.IsCancellationRequested)
return;
//continue your code keep checking for
//loop your dataset
//call _addAction?.Invoke(uc);
}
}
This is basic, really just encapsulates the logic for both points 1 & 2, you will need to adapt the code to do the search.
Next you could change your property to cancel a previous running instance, and then start another instance immediatly after something like below.
ConnectionSearch connectionSearch;
string searchPhrase;
public string SearchPhrase
{
get => searchPhrase;
set
{
//do your setter work
if(connectionSearch != null)
{
connectionSearch.Cancel();
}
connectionSearch = new ConnectionSearch(value, addConnectionUser);
connectionSearch.PerformSearch();
}
}
void addConnectionUser(object uc)
{
//pperform your add logic..
}
The code is pretty straight forward, however you will see in the setter is simply cancelling an existing request and then creating a new request. You could put some disposal cleanup logic in place but this should get you started.
You can implement some sort of debouncer which will encapsulate the logics of task result debouncing, i.e. it will assure if you run many tasks, then only the latest task result will be used:
public class TaskDebouncer<TResult>
{
public delegate void TaskDebouncerHandler(TResult result, object sender);
public event TaskDebouncerHandler OnCompleted;
public event TaskDebouncerHandler OnDebounced;
private Task _lastTask;
private object _lock = new object();
public void Run(Task<TResult> task)
{
lock (_lock)
{
_lastTask = task;
}
task.ContinueWith(t =>
{
if (t.IsFaulted)
throw t.Exception;
lock (_lock)
{
if (_lastTask == task)
{
OnCompleted?.Invoke(t.Result, this);
}
else
{
OnDebounced?.Invoke(t.Result, this);
}
}
});
}
public async Task WaitLast()
{
await _lastTask;
}
}
Then, you can just do:
private readonly TaskDebouncer<Connections[]> _connectionsDebouncer = new TaskDebouncer<Connections[]>();
public ClassName()
{
_connectionsDebouncer.OnCompleted += OnConnectionUpdate;
}
public void OnConnectionUpdate(Connections[] connections, object sender)
{
RunOnMainThread(SearchResult.Clear);
isAllFriends = false;
currentPage = 0;
foreach (var conn in connections)
RunOnMainThread(() => SearchResult.Add(new ConnectionUser(conn)));
}
private string searchPhrase;
public string SearchPhrase
{
get => searchPhrase;
set
{
SetProperty(ref searchPhrase, value);
_connectionsDebouncer.Add(RunInAsync(LoadData));
}
}
private async Task<Connection[]> LoadData()
{
return await connectionRepository
.GetConnections(currentPage, pageSize, searchPhrase)
.Where(conn => conn.Type != UserConnection.TypeEnum.Awaiting)
.ToArray();
}
It is not pretty clear what RunInAsync and RunOnMainThread methods are.
I guess, you don't actually need them.

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

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