I'm trying to get a list from a web api which I've written before. Then I'll use that list in Xamarin.Forms ListView. My code is here:
public static class DataSource
{
public static async Task<Restoran[]> GetRestoransAsync()
{
// ... Use HttpClient.
using (HttpClient client = new HttpClient())
using (HttpResponseMessage response = await client.GetAsync(page))
using (HttpContent content = response.Content)
{
// ... Read the string.
string result = await content.ReadAsStringAsync();
var restorans = JsonConvert.DeserializeObject<Restoran[]>(result);
return restorans;
}
}
}
My ContentPage:
public class MenuPage : ContentPage
{
ListView listView;
List<Restoran> restorans = new List<Restoran>();
async Task LoadRestorans()
{
restorans = (await DataSource.GetRestoransAsync()).ToList();
}
public MenuPage(string masa)
{
var loadData = LoadRestorans();
loadData.Wait();
listView = new ListView(ListViewCachingStrategy.RecycleElement)
{
ItemsSource = restorans,
ItemTemplate = new DataTemplate(() => {
var nativeCell = new CustomCell();
return nativeCell;
})
};
}
}
But when I debugged this code, "LoadRestorans()" method has called just before initialization of "restorans" list. I think I don't understand the mentality of async methods. What should I do?
You have two options.
Create the page using an async factory method
public class MenuPage : ContentPage {
ListView listView;
List<Restoran> restorans = new List<Restoran>();
private async Task LoadRestoransAsync() {
restorans = (await DataSource.GetRestoransAsync()).ToList();
listView = new ListView(ListViewCachingStrategy.RecycleElement) {
ItemsSource = restorans,
ItemTemplate = new DataTemplate(() => {
var nativeCell = new CustomCell();
return nativeCell;
})
};
}
public MenuPage(string masa) {
//...
}
public static async Task<MenuPage> CreateMenuPageAsync(string masa) {
var page = new MenuPage(masa);
await page.LoadRestoransAsync();
return pagel
}
}
Then use it like this in other async event handlers
var page = await MenuPage.CreateMenuPageAsync("<masa here>");
OR
do it in the OnAppearing event.
Subscribe to the Appearing event of the page/view
protected override void OnAppearing() {
this.Appearing += Page_Appearing;
}
and call your async code on an actual even handler
private async void Page_Appearing(object sender, EventArgs e) {
//...call async code here
//unsubscribing from the event
this.Appearing -= Page_Appearing;
}
The full class would look something like this
public class MenuPage : ContentPage {
ListView listView;
List<Restoran> restorans = new List<Restoran>();
private async Task LoadRestoransAsync() {
restorans = (await DataSource.GetRestoransAsync()).ToList();
listView = new ListView(ListViewCachingStrategy.RecycleElement) {
ItemsSource = restorans,
ItemTemplate = new DataTemplate(() => {
var nativeCell = new CustomCell();
return nativeCell;
})
};
}
public MenuPage(string masa) {
//...
}
protected override void OnAppearing() {
this.Appearing += Page_Appearing;
}
private async void Page_Appearing(object sender, EventArgs e) {
//...call async code here
await LoadRestoransAsync();
//unsubscribing from the event
this.Appearing -= Page_Appearing;
}
}
What was happening before was that by calling .Wait(), which is a blocking call the class was mixing async and blocking calls (like .Result and .Wait()) which can lead to deadlocks. Hence why you were unable to go past the method when you tried to test it.
It is call before restorans because the constructor MenuPage is called before the initialization of properties.
I would suggest
public class MenuPage : ContentPage
{
ListView listView;
List<Restoran> restorans;
async Task<Restoran[]> LoadRestorans()
{
return await DataSource.GetRestoransAsync();
}
public MenuPage(string masa)
{
this.restorans = LoadRestorans().GetAwaiter().GetResult().ToList();
listView = new ListView(ListViewCachingStrategy.RecycleElement)
{
ItemsSource = this.restorans,
ItemTemplate = new DataTemplate(() => {
var nativeCell = new CustomCell();
return nativeCell;
})
};
}
}
Note: I have not compiled this code, but this should be proper code as per my understanding.
Related
Is it possible to await on a callback from a Event Aggregator such as prism?
MessageManager.Subscribe("Reply", this.GetType().Name, Myfunc);
private void Myfunc(object obj)
{
}
public async void MyFunc2()
{
MessageManager.Publish("Request", xxx);
await [Message Reply]
}
There is the ugly method of Subscribe followed by a unsubscribe in a lambda but this is not very pretty.
Is there a better way to do that?
EDIT:
This is the solution I have so far:
private SemaphoreSlim dataReady = new SemaphoreSlim(0, 1);
MeasurementDataSet lastDataSet;
public Calibrator(IMessageManager msgMgr)
{
MessageManager = msgMgr;
MessageManager.Subscribe("NewMeasurementData", this.GetType().Name, OnMeasurementData);
}
private void OnMeasurementData(object obj)
{
lastDataSet = (MeasurementDataSet) obj;
dataReady.Release();
}
public async void Calibrate(IParameterCache prmCache, Action<string> updateText)
{
MessageManager.Publish("InitiateMeasurement", "CAL ");
await dataReady.WaitAsync();
// do stuff
MessageManager.Publish("InitiateMeasurement", "CAL ");
await dataReady.WaitAsync();
// do stuff
MessageManager.Publish("InitiateMeasurement", "CAL ");
await dataReady.WaitAsync();
// do stuff
}
You can use a TaskCompletionSource<T> to await an event
public partial class MainWindow : Window
{
IEventAggregator _eventAggregator;
public MainWindow()
{
InitializeComponent();
_eventAggregator = new EventAggregator();
_eventAggregator.GetEvent<PubSubEvent<Request>>().Subscribe( payload =>
{
Thread.Sleep( 1000 ); // only fake work
_eventAggregator.GetEvent<PubSubEvent<Response>>().Publish( new Response() );
}, ThreadOption.BackgroundThread );
}
private async void Button_Click( object sender, RoutedEventArgs e )
{
( (Button)sender ).IsEnabled = false;
try
{
var tcs = new TaskCompletionSource<Response>();
using ( var token = _eventAggregator.GetEvent<PubSubEvent<Response>>().Subscribe( payload =>
{
tcs.TrySetResult( payload );
}, ThreadOption.BackgroundThread ) )
{
_eventAggregator.GetEvent<PubSubEvent<Request>>().Publish( new Request() );
await tcs.Task;
}
}
finally
{
( (Button)sender ).IsEnabled = true;
}
}
}
public class Request
{
}
public class Response
{
}
I can't understand why the main thread doesn't give back control to process the result.
public partial class NewTravelPage : ContentPage
{
public NewTravelPage()
{
InitializeComponent();
}
protected async override void OnAppearing()
{
var locator = CrossGeolocator.Current;
var position = await locator.GetPositionAsync();
var vanues = await VenueLogic.getVenues(position.Latitude, position.Longitude);
venueListView.ItemsSource = vanues;
}
}
I call the method getVenues:
public class VenueLogic
{
public async static Task<List<Venue>> getVenues(double latitude, double longitude)
{
List<Venue> vanues = new List<Venue>();
var url = VenueRoot.GenerateUrl(latitude, longitude);
using (HttpClient client = new HttpClient())
{
var res = await client.GetAsync("https://stackoverflow.com");
// here the code gives control to the main thread and stucks
var response = await res.Content.ReadAsStringAsync();
var venueRoot = JsonConvert.DeserializeObject<VenueRoot>
(response);
vanues = venueRoot.response.venues as List<Venue>;
}
return vanues;
}
}
Used .NetStandard;
Please, help! I can't understand where the deadlock happens
Your async void on a non event handler means your fire and forget call will not be able to catch any exceptions that may have been thrown.
Reference Async/Await - Best Practices in Asynchronous Programming
Fix that by using an event handler
public partial class NewTravelPage : ContentPage {
public NewTravelPage() {
InitializeComponent();
appearing += onAppearing;
}
protected override void OnAppearing() {
appearing(this, EventArgs.Empty);
}
event EventHandler appearing = delegate { };
private async void onAppearing(object sender, EventArgs args) {
try {
var locator = CrossGeolocator.Current;
var position = await locator.GetPositionAsync();
var vanues = await VenueLogic.getVenues(position.Latitude, position.Longitude);
venueListView.ItemsSource = vanues;
} catch( Exception ex) {
//handler error (Log?)
}
}
}
Which would help in catching any exception to identify any problems.
Next, Referencing You're using HttpClient wrong
public class VenueLogic {
static HttpClient client = new HttpClient();
public async static Task<List<Venue>> getVenues(double latitude, double longitude) {
var url = VenueRoot.GenerateUrl(latitude, longitude);
var response = await client.GetAsync(url);
var jsonContent = await response.Content.ReadAsStringAsync();
var venueRoot = JsonConvert.DeserializeObject<VenueRoot>(jsonContent);
List<Venue> vanues = venueRoot.response.venues as List<Venue>;
return vanues;
}
}
create a single client and use that for the lifetime of the application.
Finally I would suggest you look into using Dependency injection to inject service instances where needed instead of using those static helpers
Reference Explicit Dependencies Principle
I have a App that uses tabbed pages, In the xaml of the Parent tab page
I populate all my other tab pages, I have a viewmodel that binds to the Parent tab page and viewmodels for each of the other Tab pages. I have a badge on one of the tabs that has a counter which shows how many messages there are.
I am having trouble updating the counter.
So I have a call to retrieve the amount of unread messages from the database which is populating into the counter on app load. When i Navigate to view the message it updates the database of that the message has been read , I then navigate back to the tabbed page with a popasync , I then pull to refresh which executes the call to get amount of messages read but it not updating the the counter, if i put a break point on the GetCounter method i see it is updating the counter with the right amount but not changing in on the badge.
Hope that makes sense.
If anyone can help i will be very grateful.
Master Tab Page:
<NavigationPage Title="Message" Icon="email.png" plugin:TabBadge.BadgeText="{Binding counter}"
plugin:TabBadge.BadgeColor="Red"
plugin:TabBadge.BadgePosition="PositionTopRight"
plugin:TabBadge.BadgeTextColor="Green">
<x:Arguments>
<local:MessagePage BindingContext="{Binding messages}" />
</x:Arguments>
</NavigationPage>
public partial class MasterTabPage : TabbedPage
{
Master_PageViewModel vm;
public MasterTabPage ()
{
InitializeComponent ();
this.BindingContext = vm = new Master_PageViewModel(Navigation);
}
}
Master Tab Page ViewModel:
public class Master_PageViewModel : INotifyPropertyChanged
{
INavigation Navigation;
private int _counter;
public int counter
{
get => _counter;
set
{
_counter = value;
OnPropertyChanged(nameof(counter));
}
}
public MessagePageViewModel messages { get; set; }
public Master_PageViewModel(INavigation navigation)
{
Navigation = navigation;
messages = new MessagePageViewModel(Navigation);
Init();
counter = 0;
}
public async void Init()
{
await GetCounter();
}
public async Task GetCounter()
{
try
{
using (HttpClient client = new HttpClient())
{
List<MessageModel> msg = new List<MessageModel>();
using (HttpResponseMessage response = await client.GetAsync("http://localhost:53665/api/GetMessagesCount/Id=" + 2 + "/" ))
{
if (response.IsSuccessStatusCode)
{
using (HttpContent content = response.Content)
{
var textresponse = await content.ReadAsStringAsync();
var json = JsonConvert.DeserializeObject<List<MessageModel>>(textresponse);
foreach (var i in json)
{
msg.Add(new MessageModel
{
msgCounter = i.msgCounter,
});
}
counter = msg[0].msgCounter;
}
}
else
{
}
}
}
}
catch (Exception)
{
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Message Tab ViewModel:
public class MessagePageViewModel : BaseViewModel
{
public ICommand MessageDetailsCommand { get; set; }
INavigation Navigation;
private ObservableCollection<MessageModel> _messagesList;
public ObservableCollection<MessageModel> MessagesList
{
get { return _messagesList; }
set
{
if (_messagesList != value)
{
_messagesList = value;
}
}
}
public ICommand ReloadCommand { get; set; }
public ICommand RefreshCommand
{
get
{
return new Command(async () =>
{
await GetMessages();
Master_PageViewModel vm = new Master_PageViewModel(Navigation,multiMediaPickerService);
await vm.GetCounter();
});
}
}
bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
_isBusy = value;
}
}
public MessagePageViewModel(INavigation navigation)
{
ReloadCommand = new Command(async () => await ReloadPage());
Navigation = navigation;
MessageDetailsCommand = new Command(async (object obj) => await MessageDetails(obj));
Initialize();
}
private async void Initialize()
{
await GetMessages();
}
private async Task ReloadPage()
{
await GetMessages();
}
public async Task GetMessages()
{
List<MessageModel> msg = new List<MessageModel>
.........
MessagesList = new ObservableCollection<MessageModel>(msg);
}
private async Task MessageDetails(object obj)
{
var item = (obj as MessageModel);
await Navigation.PushAsync(new MessageDetailsPage(....));
}
}
}
}
This is because you created a new instance of Master_PageViewModel in your RefreshCommand. It is not the parent tabbed page's binding context so the tab's badge won't be updated even though the GetCounter has been triggered.
You have to pass the parent tabbed view model to your MessagePageViewModel like:
public Master_PageViewModel(INavigation navigation)
{
Navigation = navigation;
messages = new MessagePageViewModel(Navigation, this);
Init();
counter = 0;
}
And change your message page view model's constructor:
Master_PageViewModel parentViewModel
public MessagePageViewModel(INavigation navigation, Master_PageViewModel viewModel)
{
ReloadCommand = new Command(async () => await ReloadPage());
Navigation = navigation;
parentViewModel = viewModel;
// ...
}
At last, trigger the method in your refresh command:
public ICommand RefreshCommand
{
get
{
return new Command(async () =>
{
await GetMessages();
await parentViewModel.GetCounter();
});
}
}
Moreover, I noticed that your MessagePageViewModel used the parent tabbed view model's navigation. I don't think this is a good approach as it has its own NavigationPage so that it should utilize its own navigation instead of the parent's.
So far I could make a delegate type, for example:
// Can't use Task in WinRT interface and TypedEventHandler doesn't work with async await
public delegate IAsyncOperation<string> AsyncEventHandler(object sender, object args);
And then expose in WinRT object like so:
public AsyncEventHandler OnMyEvent { get; set; }
In the WinRT object I would call it like this:
if (OnMyEvent != null)
{
var result = await OnMyEvent.Invoke(this, someArgs);
// Do something with the result...
}
And the client code consuming the WinRT object could do this:
instanceOfWinRTObject.OnMyEvent = OnCalledBackFromWinRT;
But because the delegate returns an IAsyncOperation we need to do some wrapping:
private async Task<string> OnCalledBackFromWinRTAsync(object sender,
object args)
{
return await GetSomeStringAsync(args);
}
private IAsyncOperation<string> OnCalledBackFromWinRT(object sender, object args)
{
return OnCalledBackFromWinRTAsync(sender, args).AsAsyncOperation();
}
It just feels like there must be a cleaner way to achieve this.
Here is an alternative prompted by Peter Torr's comment.
// Custom event args class
public sealed class MyEventArgs
{
public string Result;
public Deferral Deferral;
}
// Declare the event handler on the WinRT component
public event TypedEventHandler<object, MyEventArgs> OnSuspendingEvent;
Then in the WinRT component you could invoke the event like this:
if (OnSuspendingEvent != null)
{
var tcs = new TaskCompletionSource();
using (var deferral = new Deferral(() => tcs.SetResult(true)))
{
var myArgs = MyEventArgs();
myArgs.Deferral = deferral;
OnSuspendUnloading.Invoke(this, myArgs);
await tcs.Task;
DoSomethingWithResult(args.Result);
}
}
And finally in the client code add a handler like so:
instanceOfWinRTObject.OnSuspendingEvent += async (sender, args) =>
{
var deferral = args.Deferral;
try
{
// Client does some async work with a result
args.Result = await GetSomeStringAsync(args);
}
finally
{
deferral.Complete();
}
}
This is a followup question to the following question:
Volatile IEnlistmentNotification and TransactionScope.AsyncFlowEnabled = true
The approach accepted in the question above works as long as you don't await multiple statements. Let me show an example:
public class SendResourceManager : IEnlistmentNotification
{
private readonly Action onCommit;
public SendResourceManager(Action onCommit)
{
this.onCommit = onCommit;
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
preparingEnlistment.Prepared();
}
public void Commit(Enlistment enlistment)
{
Debug.WriteLine("Committing");
this.onCommit();
Debug.WriteLine("Committed");
enlistment.Done();
}
public void Rollback(Enlistment enlistment)
{
enlistment.Done();
}
public void InDoubt(Enlistment enlistment)
{
enlistment.Done();
}
}
public class AsyncTransactionalMessageSender : ISendMessagesAsync
{
private readonly List<Message> sentMessages = new List<Message>();
public IReadOnlyCollection<Message> SentMessages
{
get { return new ReadOnlyCollection<Message>(this.sentMessages); }
}
public async Task SendAsync(Message message)
{
if (Transaction.Current != null)
{
await Transaction.Current.EnlistVolatileAsync(
new SendResourceManager(async () => await this.SendInternal(message)),
EnlistmentOptions.None);
}
else
{
await this.SendInternal(message);
}
}
private async Task SendInternal(Message message)
{
Debug.WriteLine("Sending");
await Task.Delay(1000);
this.sentMessages.Add(message);
Debug.WriteLine("Sent");
}
}
[Test]
public async Task ScopeRollbackAsync_DoesntSend()
{
var sender = new AsyncTransactionalMessageSender();
using (var tx = new System.Transactions.TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await sender.SendAsync(new Message("First"));
await sender.SendAsync(new Message("Second"));
await sender.SendAsync(new Message("Last"));
// We do not commit the scope
}
sender.SentMessages.Should().BeEmpty();
}
[Test]
public async Task ScopeCompleteAsync_Sends()
{
var sender = new AsyncTransactionalMessageSender();
using (var tx = new System.Transactions.TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await sender.SendAsync(new Message("First"));
await sender.SendAsync(new Message("Second"));
await sender.SendAsync(new Message("Last"));
tx.Complete();
}
sender.SentMessages.Should().HaveCount(3)
.And.Contain(m => m.Value == "First")
.And.Contain(m => m.Value == "Second")
.And.Contain(m => m.Value == "Last");
}
As soon as you introduce a Task.Delay like shown in the example above the generated asynchronous statemachine will never come back and invoke the this.sentMessages.Add(message) and Debug.WriteLine("Sent")
The problem is I currently see now way to properly enlist asynchronous code inside the enlistment notification. Any ideas how to tackle this challenge?