cancel taskcompletionsource which calls a void method from an API with timeout xamarin forms - xamarin

I have this non-async Task> which just requests:
TaskCompletionSource<ObservableCollection<ItemDto>> tcs = new TaskCompletionSource<ObservableCollection<ItemDto>>();
ObservableCollection<ItemDto> results = new ObservableCollection<ItemDto>();
try
{
BasicHttpBinding binding = new BasicHttpBinding();
binding.OpenTimeout = new TimeSpan(0, 0, 30);
binding.CloseTimeout = new TimeSpan(0, 0, 30);
binding.SendTimeout = new TimeSpan(0, 0, 30);
binding.ReceiveTimeout = new TimeSpan(0, 0, 30);
MobileClient clientMobile = new MobileClient(binding, new EndpointAddress(_endpointUrl));
clientMobile.FindItemsCompleted += (object sender, FindItemsCompletedEventArgs e) =>
{
if (e.Error != null)
{
_error = e.Error.Message;
tcs.TrySetException(e.Error);
}
else if (e.Cancelled)
{
_error = "Cancelled";
tcs.TrySetCanceled();
}
if (string.IsNullOrWhiteSpace(_error) && e.Result.Count() > 0)
{
results = SetItemList(e.Result);
tcs.TrySetResult(results);
}
clientMobile.CloseAsync();
};
clientMobile.FindItemsAsync(SetSearchParam(searchString, 100));
}
catch (Exception)
{
results = new ObservableCollection<ItemDto>();
tcs.TrySetResult(results);
}
return tcs.Task;
Yes, I know, nothing special, it's just that this
clientMobile.FindItemsAsync(SetSearchParam(searchString, 100))
is a call to a void method, which in turn calls another void method which sets a few params in order to then call an async method which itself calls an async method which performs an async operation to return the list of Items.
Problem is, I have no control whatsoever of anything beyond the scope of this Task above, because everything I just explained is part of an API, in which I'm not allowed to touch, and of which I can make no comment, regarding the way it works, as the policy is for me to adapt my work to it... -_-
So, in order to do that, I must kill this call to the FindItemsAsync, as soon as a total of 1 minute has passed... I tried setting the above timespans to a minute each (first worked, now some changes have been made and no go), I tried reducing to half the time, and no go...
Here's the code which is calling this Task:
public void LoadItemList(string searchString)
{
_itemList = new ObservableCollection<ItemDto>();
// Calls the Task LoadList.
var result = LoadList(searchString).Result;
if (result != null && result != new ObservableCollection<ItemDto>())
{
_itemList = result;
}
else
{
_isTaskCompleted = false;
}
_isListEmpty = (_itemList != new ObservableCollection<ItemDto>()) ? false : true;
}
and below is the code which calls the caller of this task... (what a mess -_-):
void Init(string searchString = "")
{
Device.BeginInvokeOnMainThread(async () =>
{
if (!LoadingStackLayout.IsVisible && !LoadingActivityIndicator.IsRunning)
{
ToggleDisplayLoadingListView(true);
}
await Task.Run(() => _listVM.LoadItemList(searchString));
ToggleDisplayLoadingListView();
if (!string.IsNullOrWhiteSpace(_listVM.Error))
{
await DisplayAlert("Error", _listVM.Error, "OK");
}
else if (_listVM.AdList != null && !_listVM.IsListEmpty)
{
ItemListView.IsVisible = true;
ItemListView.ItemsSource = _listVM.ItemList;
}
else if (!_listVM.IsTaskCompleted || _listVM.IsListEmpty)
{
await DisplayAlert("", "At the moment it is not possible to show results for your search.", "OK");
}
else if (_listVM.ItemList.Count == 0)
{
await DisplayAlert("", "At the moment there are no results for your search.", "OK");
}
});
}
At the moment I'm trying to implement the MVVM arch...
Really, thank you so much for your help on this matter, it's been great, and I really apologize for all this inconvenience...
EDIT
Sorry because I didn't explain my objective clearly; it is: I need to fetch a list of Items accessing an API that just communicates with me via a void method FindItemsAsync. I have 60 seconds to fetch all those items. If something goes wrong, or if timeout, I have to cancel the process and inform the user something went wrong.
That doesn't happen. It never cancels. Either gets me the Items, or stays loading forever, dispite my hardest tries... I'm new to tasks and most of this stuff, hence my constant issues...

You can call CloseAsync when your cancellation token expires.
//Creates an object which cancels itself after 5000 ms
var cancel = new CancellationTokenSource(5000);
//Give "cancel.Token" as a submethod parameter
public void SomeMethod(CancellationToken cancelToken)
{
...
//Then use the CancellationToken to force close the connection once you created it
cancelToken.Register(()=> clientMobile.CloseAsync());
}
It will cut down the connection.

Related

Xamarin Forms picker SelectedIndexChange null exception

I have created an application that asks for a username and returns the stats for this user. When a counter is more than 1 it focuses on a picker and asks to choose one of the options. After, the SelectedIndexChanged (Platformselect_SelectedIndexChanged) method is being called and gets the stats. The problem is that if the user enters a username, the first time it works. When the user goes back and re-enters the username it doesn't focus (it doesn't show the picker) and automatically calls the SelectedIndexChanged method with platformselect.SelectedIndex being -1 causing my program to crash. Could you please help me?
I have tried to set focus on the main thread, I have tested it in both iOS and Android and the same problem appears.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
platformselect.HeightRequest = 0;
this.search.Clicked += Button_Clicked;
}
public async void searchStats(string usernameText)
{
search.IsEnabled = false;
activityIndicator.IsRunning = true;
var id = await GetDataLogic.GetID(usernameText);
publicid = id;
activityIndicator.IsRunning = false;
if (id.uid == "error")
{
await DisplayAlert("Error", "Could not find username: " + usernameText, "OK");
search.IsEnabled = true;
return;
}
else if (id.uid == "errornointernet")
{
await DisplayAlert("Error", "Could not connect to the Internet. Please check your connection and try again.", "OK");
search.IsEnabled = true;
return;
}
else
{
if(id.platforms.Count > 1)
{
platformselect.ItemsSource = publicid.platforms.ToList();
//we have to show alert to choose platform and then call get user stats.
**Device.BeginInvokeOnMainThread(() =>
{
if (platformselect.IsFocused)
platformselect.Unfocus();
platformselect.Focus();
});**
}
else
{
//we call it without asking because there is only one platform.
var userstats = await GetDataLogic.GetStats(id, 0);
await Navigation.PushAsync(new Stats(userstats, favorites));
}
}
}
private async void Platformselect_SelectedIndexChanged(object sender, EventArgs e)
{
//the second time it doesnt show the picker and it automatically enters -1... it returns null exception. Has to be investigated.
var userstats = await GetDataLogic.GetStats(publicid, platformselect.SelectedIndex);
await Navigation.PushAsync(new Stats(userstats, favorites));
}
}
I would like it to always focus on the picker and wait for the response for the user, not automatically call SelectedIndexChanged with -1 as the selectedindex
I suggest change your event handler this way
private async void Platformselect_SelectedIndexChanged(object sender, EventArgs e)
{
//Check for unselection event
if(platformselect.SelectedIndex == -1) return;
var userstats = await GetDataLogic.GetStats(publicid, platformselect.SelectedIndex);
await Navigation.PushAsync(new Stats(userstats, favorites));
}

How to allow the user only if he/she type the “ivr” or “IVR” using Form Flow concept

I am working on bot framework technology, in one of my current project I want to allow the user only if he/she type the ‘’ivr” or “IVR” otherwise it shows some feedback to the user.
For that I have wrote below lines of code, but this code shows some wrong output to the user. Even if the user enter ivr or IVR it shows feedback to the user for the first time, but from second time onwards its working correctly.
[Serializable]
class Customer
{
//Create Account Template
[Prompt("Please send any of these commands like **IVR** (or) **ivr**.")]
public string StartingWord;
public static IForm<Customer> BuildForm()
{
OnCompletionAsyncDelegate<Customer> accountStatus = async (context, state) =>
{
await Task.Delay(TimeSpan.FromSeconds(5));
await context.PostAsync("We are currently processing your account details. We will message you the status.");
};
var builder = new FormBuilder<Customer>();
return builder
//.Message("Welcome to the BankIVR bot! To start an conversation with this bot send **ivr** or **IVR** command.\r \n if you need help, send the **Help** command")
.Field(nameof(Customer.StartingWord), validate: async (state, response) =>
{
var result = new ValidateResult { IsValid = true, Value = response };
string str = (response as string);
if (str.ToLower() != "ivr")
{
result.Feedback = "I'm sorry. I didn't understand you.";
result.IsValid = false;
return result;
}
else if (str.ToLower() == "ivr")
{
result.IsValid = true;
return result;
}
else
{
return result;
}
})
.OnCompletion(accountStatus)
.Build();
}
};
Please tell me how to resolve this issue using Form Flow concept.
-Pradeep
Your code looks correct to me - I can only suggest you debug your code with a step-through debugger and see where the logic tests are failing.
That said, if it's not working for people in Turkey, it's because you shouldn't use .ToLower() for normalizing text, for example the .ToLower() method does not work for text that contains the Turkish dotless 'I' character: http://archives.miloush.net/michkap/archive/2004/12/02/273619.html
Also, your else case will never be hit because your two prior checks (!= and ==) cover every possible case (the C# compiler is currently not sophisticated enough to flag the else case as unreachable code).
The correct way to do a case-insensitive comparison is with String.Equals:
if( "ivr".Equals( str, StringComparison.InvariantCultureIgnoreCase ) ) {
result.IsValid = true;
return result;
}
else {
result.Feedback = "I'm sorry. I didn't understand you.";
result.IsValid = false;
}
Finally, I got the result with out any issue.
here is my updated code for to allow only the user enter "ivr or IVR" word, to start a form flow conversation with bot.
.Field(nameof(Customer.StartingWord), validate: async (state, response) =>
{
var result = new ValidateResult { IsValid = true, Value = response };
string str = (response as string);
if ("ivr".Equals(str, StringComparison.InvariantCultureIgnoreCase))
{
//result.IsValid = true;
//return result;
}
else
{
result.Feedback = "I'm sorry. I didn't understand you.";
result.IsValid = false;
//return result;
}
return result;
})
-Pradeep

Windows Phone 7 - wait for Webclient to complete

I'm developing an app and have run into a problem with asynchronous calls... Here's what i'm trying to do.
The app consumes a JSON API, and, when run, fills the ListBox within a panorama item with the necessary values (i.e. a single news article). When a user selects a ListBox item, the SelectionChanged event is fired - it picks up the articleID from the selected item, and passes it to an Update method to download the JSON response for the article, deserialize it with JSON.NET, and taking the user to the WebBrowser control which renders a html page from the response received.
The problem with this is that I have to wait for the response before I start the NavigationService, but I'm not sure how to do that properly. This way, the code runs "too fast" and I don't get my response in time to render the page.
The event code:
private void lstNews_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (lstNews.SelectedIndex == -1)
{
return;
}
ShowArticle _article = new ShowArticle();
ListBox lb = (ListBox)sender;
GetArticles item = (GetArticles)lb.SelectedItem;
string passId = ApiRepository.ApiEndpoints.GetArticleResponseByID(item.Id);
App.Current.JsonModel.JsonUri = passId;
App.Current.JsonModel.Update();
lstNews.SelectedIndex = -1;
NavigationService.Navigate(new Uri("/View.xaml?id=" + item.Id, UriKind.Relative));
}
OnNavigatedTo method in the View:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
long sentString = long.Parse(NavigationContext.QueryString["id"]);
string articleUri = ApiRepository.ApiEndpoints.GetArticleResponseByID(Convert.ToInt32(sentString));
//this throws an error, runs "too fast"
_article = App.Current.JsonModel.ArticleItems[0];
}
The update method:
public void Update()
{
ShowArticle article = new ShowArticle();
try
{
webClient.DownloadStringCompleted += (p, q) =>
{
if (q.Error == null)
{
var deserialized = JsonConvert.DeserializeObject<ShowArticle>(q.Result);
_articleItems.Clear();
_articleItems.Add(deserialized);
}
};
}
catch (Exception ex)
{
//ignore this
}
webClient.DownloadStringAsync(new Uri(jsonUri));
}
async callback pattern:
public void Update(Action callback, Action<Exception> error)
{
webClient.DownloadStringCompleted += (p, q) =>
{
if (q.Error == null)
{
// do something
callback();
}
else
{
error(q.Error);
}
};
webClient.DownloadStringAsync(new Uri(jsonUri));
}
call:
App.Current.JsonModel.Update(() =>
{
// executes after async completion
NavigationService.Navigate(new Uri("/View.xaml?id=" + item.Id, UriKind.Relative));
},
(error) =>
{
// error handling
});
// executes just after async call above

BackKey not working when a Thread is running. Why?

PageA Navigated to PageB
There is a thread is running for HttpWebRequest.
Back Key is invalid when the Thread is running.
PageB Code:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
Debug.WriteLine("OnNavigatedTo");
//base.OnNavigatedTo(e);
DoWork();
}
void DoWork()
{
t = new Thread(new ThreadStart(() =>
{
request = WebRequest.Create("http://www.google.com") as HttpWebRequest;
request.BeginGetResponse(new AsyncCallback(AsyncBack), request);
}));
t.IsBackground = true;
t.Start();
}
void AsyncBack(IAsyncResult ias)
{
HttpWebRequest req = (HttpWebRequest)ias.AsyncState;
using (HttpWebResponse res = req.EndGetResponse(ias) as HttpWebResponse)
{
this.Dispatcher.BeginInvoke(() =>
{
this.PageTitle.Text = res.ContentLength.ToString();
long length = res.ContentLength;
for (long i = 0; i < length; i++)
{
//here imitate a long time for working
Debug.WriteLine(i);
if (i == length)
{
break;
}
}
Debug.WriteLine(res.ContentLength);
});
}
}
the Back Key is invalid until Method AsyncBack() is done.
'Back Key is invalid' Is that the app is not back to PageA when you touch the Back Key until Method AsyncBack() Done.
Why? Help me?
Why the bloody hell are you wrapping a async request in a custom thread? That doesn't even remotely make sense.
Then again, your question doesn't make much sense either, but most likely the error is related to the request is attempting to invoke a operation, via. the dispatcher, on the wrong page.
In your code you block UI thread for a long time, so you can't navigate page back, because it happens also on UI thread, put into Dispatcher only code that can't be executed not on UI.
void DoWork()
{
HttpWebRequest request = WebRequest.Create("http://www.googl.com") as HttpWebRequest;
request.BeginGetResponse(new AsyncCallback(AsyncBack), request);
}
void AsyncBack(IAsyncResult ias)
{
HttpWebRequest req = (HttpWebRequest)ias.AsyncState;
using (HttpWebResponse res = req.EndGetResponse(ias) as HttpWebResponse)
{
this.Dispatcher.BeginInvoke(() =>
{
this.PageTitle.Text = res.ContentLength.ToString();
});
long length = res.ContentLength;
for (long i = 0; i < length; i++)
{
long i_ = i;
//here imitate a long time for working
Thread.Sleep(10);
this.Dispatcher.BeginInvoke(() =>
{
this.PageTitle.Text = i_.ToString();
});
if (i == length)
{
break;
}
}
Debug.WriteLine(res.ContentLength);
}
}

WP7 WebClient DownloadStringAsync and Map

I'm using WebClient object in to poll some data from server.
It's working good and it's updating text block fine. Till I don't use map on same Page. When I add a map, only one request get completed and data is retrieved only once.
This is the code for getting messages:
public MessagesPage()
{
InitializeComponent();
new System.Threading.Timer(messagePolling, null, 0, 5000); // every 5 seconds
}
void messagePolling(object state)
{
getMessages(Const.GET_MESSAGES_URL + uuid);
}
private void getMessages(string uri)
{
WebClient webClient = new WebClient();
webClient.DownloadStringAsync(new Uri(uri));
webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(messagesResponseCompleted);
}
void messagesResponseCompleted(object sender, DownloadStringCompletedEventArgs e)
{
lock (this)
{
try
{
string s = e.Result;
if (s.Length > 0)
{
List<Message> messagesResult = JSONHelper.Deserialize<List<Message>>(s);
foreach (Message m in messagesResult)
{
tbMessages.Text += m.message + "\n";
}
}
else
{
tbMessages.Text += "No new messages #: " + System.DateTime.Now + "\n";
}
}
catch (System.Net.WebException we)
{
MessageBox.Show(we.Message);
}
}
}
Anyone?
The WebClient response is processed on the UI thread - so you don't need the lock that you have in your event handler.
For your particular problem - is this just occurring in the emulator? I've seen quite a few timer issues with the emulator - but never anything similar on the real phone.
As an aside, I believe in general it's better to use HttpWebRequest rather than WebClient - see the explanation here of webclient using the UI thread Silverlight Background Thread using WebClient - for your particular code I don't think this will be a problem.
If using
System.Windows.Threading.DispatcherTimer myDispatcherTimer = new System.Windows.Threading.DispatcherTimer();
myDispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 5000);
myDispatcherTimer.Tick += new EventHandler(messagePolling);
myDispatcherTimer.Start();
instead of
new System.Threading.Timer(messagePolling, null, 0, 5000); // every 5 seconds
is working fine =)

Resources