We have existing iOS application developed using Xamarin.Forms. Now we want to extend to both Android and Windows Phone. In the existing application, all the web service calls are made synchronously. Windows phone supports only asynchronous calling, so we thought of wrapping the asynchronous calls to synchronous.
We are using HttpClient.PostAsync method to access the service. Once the execution hits PostAsync method, the phone hangs. The code to call the service is as follows:
private static async void CallService(System.Uri uri)
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Host = uri.Host;
client.Timeout = System.TimeSpan.FromSeconds(30);
HttpContent content = new StringContent("", Encoding.UTF8, "application/xml");
var configuredClient = client.PostAsync(uri, content).ConfigureAwait(false);
var resp = configuredClient.GetAwaiter().GetResult();
resp.EnsureSuccessStatusCode();
responseString = resp.StatusCode.ToString();
resp.Dispose();
client.CancelPendingRequests();
client.Dispose();
}
}
I know this is because of blocking the UI thread, so only I implemented ConfigureAwait(false) but that didn't work at all. I tried with System.Net.WebClient also but the same result.
Now, how I will make this asynchronous call to process synchronously in Windows Phone 8?
First of all avoid using async void methods,because you can't easily wait for its completion. Return Task instead, being inside async method you don't need to do something special to return a Task. Compiler does all the work for you.
You need to await the call to HttpClient.PostAsync, that should be enough to keep the UI responsive.
private static async Task CallService(System.Uri uri)
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Host = uri.Host;
client.Timeout = System.TimeSpan.FromSeconds(30);
HttpContent content = new StringContent("", Encoding.UTF8, "application/xml");
var resp = await client.PostAsync(uri, content);// Await it
resp.EnsureSuccessStatusCode();
responseString = resp.StatusCode.ToString();
resp.Dispose();
client.CancelPendingRequests();
}
}
Note: I've removed the ConfigureAwait(false) as that's not required. If you really need it, you may add it back.
Related
I have this method that downloads data from the internet:
public ObservableCollection<Magnetka> ParseJSON() {
ObservableCollection<Models.Magnetka> Markers = new ObservableCollection<Models.Magnetka>();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("(URL)");
request.Headers.Add("Authentication-Token", "(KEY)");
request.Method = "GET";
request.ContentType = "application/json";
CookieContainer cookieContainer = new CookieContainer();
request.CookieContainer = cookieContainer;
var response = request.GetResponse();
var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
......... JSON parsing)
return Markers;
Now I found out this isn't too fast and it freezes the app for two seconds or so, so I need to make it async. However, I call the method like this in another class:
GetMarkers getMarkers = new GetMarkers();
Markers = getMarkers.ParseJSON();
How can I make my method async? I don't understand the concept well and if I make ParseJSON() async Task, I don't know where to put the "await" keyword.
Can anyone help me?
first, mark the method async and use the async version of GetResponse
public async Task<ObservableCollection<Magnetka>> ParseJSON()
{
...
var response = await request.GetResponseAsync();
...
return Markers;
}
then when you call it use the await keyword
Markers = await getMarkers.ParseJSON();
You would change your method to something like this instead:
public async Task<ObservableCollection<Magnetka>> ParseJSON()
{
var markers = new ObservableCollection<Magnetka>();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("(URL)");
request.Headers.Add("Authentication-Token", "(KEY)");
request.Method = "GET";
request.ContentType = "application/json";
CookieContainer cookieContainer = new CookieContainer();
request.CookieContainer = cookieContainer;
using var response = await request.GetResponseAsync().ConfigureAwait(false);
using var responseStream = response.GetResponseStream();
using var streamReader = new StreamReader(responseStream);
var json = await streamReader.ReadToEndAsync().ConfigureAwait(false);
// json parsing
return markers;
}
Firstly, you would mark your method async. This signals the compiler to set up a state machine for this method for continuation. Nothing async happening yet though.
Another thing you need to do is to wrap your return value in a Task, which is similar to a promise in some other languages. Tasks are await-able, which is where the asynchronous stuff is happening.
Switching from using the synchronous methods in WebRequest to use the Async variants and awaiting their results makes your method async.
So from:
request.GetResponse()
To:
await request.GetResponseAsync()
Which returns a awaitable Task<WebResponse>.
The same goes for the stream reader code, you can use its async API too.
A tricky thing by using asynchronous code is that, it kind of forces you to do this all the way up. However, there are exceptions for lifecycle methods and events, where you don't necessarily have to change the signature to return a Task.
Another thing you might notice here. I've added ConfigureAwait(false) at the end of the async method calls. I've done this because out of the box, after returning from an awaited method, the code will try to return to the originating context. This can lead to a couple of issues such as dead-locks if the originating context is the UI Thread. Or, it can lead to bad performance trying to switch between context a lot. Adding ConfigureAwait(false) will just return on the same context the await was run on, which is fine as long as we don't need or expect to return on that context.
As for where you call ParseJson() is probably equally important. Consider deferring it to some lifecycle method or event where you are not doing much else.
This could be OnResume() on Android or ViewWillAppear on iOS. If you are using MVVM, use a Command to encapsulate it.
If I make a web service request and background the iOS app and foreground it again, I get an exception that the request has been canceled. Is this a bug or the way iOS operates? I can't find any documentation on this. What's the right way to implement a reliable web service mechanism using Xamarin Forms? I followed the HttpClient example as documented here.
This works for me both in the foreground and in the background.
maybe you also need to enable background modes in your info.plist
private async Task<string> UsaHttpClient()
do
{
...
...
...
await Task.Run(async () =>
{
HttpClient Client = new HttpClient();
Client.BaseAddress = new Uri("https://services.xxxxx.com/ServiceApi/");
var response = Client.GetStringAsync("api/function?Parametro...").Result;
HttpResponseMessage resp = new HttpResponseMessage();
resp.Content = new StringContent(response, Encoding.UTF8, "application/json");
r = response;
resp.Dispose();
Client.Dispose();
await Task.Delay(second for delay);
});
} while (X == anyCondition);
}
I'm implementing code from a developer that are using async functions.
Example:
public async Task<string> GetDataAsync(string jsonString = "")
{
string url = $"{this.url}";
using (HttpClient httpClient = new HttpClient())
{
using (StringContent body = new StringContent(jsonString, Encoding.UTF8, "application/json"))
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", this.accessToken);
using (HttpResponseMessage response = await httpClient.PostAsync(url, body))
{
switch (response.IsSuccessStatusCode)
{
case true:
return await response.Content.ReadAsStringAsync();
default:
throw new Exception($"Error calling url: {url}. HTTP status: {response.StatusCode}");
}
}
}
}
}
And I don't need, nor do I want, to call anything asynchronously. But the problem is that async functions is bubbling all the way up through my functions and so I can't just stop using it, and since HttpClient doesn't have a Post() function to use instead of PostAnync() then I feel stuck in this asynchronous cage.
Is there a trick or whatever to call an async function normally, stopping threading from bubbling up through all parent functions?
Or is the only solution to find a package without async functions?
The short answer is - no, there's no generic way to make a synchronous function out of a a task-based asynchronous one.
The problem is that you don't know how it is implemented internally. Say, the asynchronous function is implemented using async and it's running (partially) in the context of the primary thread. Then, if the caller code is trying to block the primary thread by a blocking call, then the async function is blocked, too, which is causing a deadlock.
But, in your particular case you can try to create a new task, call the async function from that task and take its result. There are good chances that it will work, but no guarantee (as mentioned above).
The code would look like this:
using (var response = Task.Run(() => httpClient.PostAsync(url, body).Result).Result)
{
...
}
I am trying to call a WCF service from a windows phone 8.1 app, the option to add a service reference no longer exists.
I tried this:
HttpClient httpClient = new System.Net.Http.HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://serverURl/serviceName.svc/methodName?variableName=value");
HttpResponseMessage response = await httpClient.SendAsync(request);
string data = await response.Content.ReadAsStringAsync();
But it didnt work, the string is always empty.
N.B The server is also not local host so im not facing the problem of connecting from windows phone emulator to local host.
Try this.
If its not working, let me know.
HttpClient httpClient = new System.Net.Http.HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get,"http://localhost:18362/Service1.svc/GetData");
HttpResponseMessage response = await httpClient.SendAsync(request);
string data = await response.Content.ReadAsStringAsync();
var dialog = new MessageDialog(data);
await dialog.ShowAsync();
Windows Phone Store apps in Windows Phone 8.1 do not support the System.ServiceModel namespace.
There is a workaround you can
Read here.
You can use System.Net.Http :
public async Task<string> GetMethod(string url)
{
HttpClient client = new HttpClient();
var response = await client.GetAsync(url); // The Get Process to get result from WCF service
string result = await response.Content.ReadAsStringAsync(); // Get Json string result
return result;
}
}
I have the following, wher I try to download a string from the server:
HttpClient client = new HttpClient();
var getResponsestring = await client.GetStringAsync("url");
But how do I go about and the if the server does not return the string I want, but error 401 for instance? Or any web error for that mather
You have two choices. Either break down the request into two steps.
HttpClient client = new HttpClient();
var response = await client.GetAsync("url");
if (response.IsSuccessStatusCode) {
var getResponsestring = await response.Content.ReadAsStringAsync();
}
or you insert a new MessageHandler that will return a stock response on errors.
var errorMessageHandler = new ErrorMessageHandler(new HttpClientHandler());
HttpClient client = new HttpClient(errorMessageHandler);
var getResponsestring = await client.GetStringAsync("url");
You will have to implement the ErrorMessageHandler yourself by deriving from a DelegatingHandler and overriding SendAsync.
HttpClient.GetStringAsync Method returns Task which has Exception property.
Update
When we use async/await we can just wrap the awaitable call in try/catch block to handle exceptions.