async method callback with Task.ContinueWIth? - c#

I have a method that pulls some HTML via the HttpClient like so:
public static HttpClient web = new HttpClient();
public static async Task<string> GetHTMLDataAsync(string url)
{
string responseBodyAsText = "";
try
{
HttpResponseMessage response = await web.GetAsync(url);
response.EnsureSuccessStatusCode();
responseBodyAsText = await response.Content.ReadAsStringAsync();
}
catch (Exception e)
{
// Error handling
}
return responseBodyAsText;
}
I have another method that looks like so:
private void HtmlReadComplete(string data)
{
// do something with the data
}
I would like to be able to call GetHTMLDataAsync and then have it call HtmlReadComplete on the UI thread when the html has been read. I naively thought this could somehow be done with something that looks like
GetHTMLDataAsync(url).ContinueWith(HtmlReadComplete);
But, I can't get the syntax correct, nor am I even sure that's the appropriate way to handle it.
Thanks in advance!

public async void ProcessHTMLData(string url)
{
string HTMLData = await GetHTMLDataAsync(url);
HTMLReadComplete(HTMLData);
}
or even
public async void ProcessHTMLData(string url)
{
HTMLReadComplete(await GetHTMLDataAsync(url));
}

You're close, but ContinueWith() takes a delegate with Task as its parameter, so you can do:
GetHTMLDataAsync(url).ContinueWith(t => HtmlReadComplete(t.Result));
Normally, you should be careful with using Result together with async, because Result blocks if the Task hasn't finished yet. But in this case, you know for sure that the Task is complete, you Result won't block.

Related

Program ends before async result is received

I am writing a program to interact with the Spotify API via a command line.
I have some code here to take a command, and then execute the relevant function to retrieve data from Spotify.
This code shows the problem, I have left out irrelevant code.
public class CommandHandler
{
public async void HandleCommands()
{
var spotifyCommand = GetCommand();
if (spotifyCommand == SpotifyCommand.Current)
{
WriteCurrentSong(await new PlayerController().GetCurrentlyPlayingAsync());
}
if (spotifyCommand == SpotifyCommand.NextTrack)
{
WriteCurrentSong(await new PlayerController().NextTrackAsync());
}
Console.ReadLine();
//end of program
}
}
public class PlayerController
{
public async Task<SpotifyCurrentlyPlaying> NextTrackAsync()
{
using (var httpClient = new HttpClient())
{
//removed code to set headers etc
//Skip Track
var response = await httpClient.PostAsync("https://api.spotify.com/v1/me/player/next", null);
if (response.StatusCode != HttpStatusCode.NoContent)
{
//code to handle this case, not important
}
return await GetCurrentlyPlayingAsync();
}
}
public async Task<SpotifyCurrentlyPlaying> GetCurrentlyPlayingAsync()
{
using (var httpClient = new HttpClient())
{
//removed code to set headers etc
var response = await httpClient.GetAsync("https://api.spotify.com/v1/me/player/currently-playing");
response.EnsureSuccessStatusCode();
return JsonSerializer.Deserialize<SpotifyCurrentlyPlaying>(await response.Content.ReadAsStringAsync());
}
}
}
The two if statements in HandleCommands() call into PlayerController and await the result of the method. For some reason if I use await PlayerController.MethodCall() the call is made, however, the result does not return before the program finishes executing.
Strangely, this is not an issue if I use PlayerController.MethodCall().Result.
Any help will be greatly appreciated, as I would really rather not use .Result. Thanks!
Signature of the HandleCommands is an issue
public async void HandleCommands()
{
// ...
}
You are not showing how this method is called, but I assume it is something like below:
var handler = new CommandHandler();
handler.HandleCommands();
Because of async void method doesn't return Task and caller can not "observe" it's completion.
So application finishes without waiting for task to complete.
To fix - change method signature to below and await for task to complete
public async Task HandleCommands()
{
// ...
}
var handler = new CommandHandler();
await handler.HandleCommands();

UWP await on HttpClient not working

I am trying get a JSON response from a web API.
I am able to retrive response in Console application with similar code, however in UWP await httpClient.GetAsync(uri); does not work as expected.
public static async Task<double> GetJson()
{
using (var httpClient = new HttpClient())
{
Uri uri= new Uri("https://api.cryptonator.com/api/ticker/btc-usd");
HttpResponseMessage response = await httpClient.GetAsync(uri);
//Below code is not relevent since code is failing on await
var result = response.Content.ReadAsStringAsync();
var jsonResponse = Json.ToObjectAsync<ExchangeRate>(result);//
jsonResponse.Wait();//
ExchangeRate exchangeRateObj = jsonResponse.Result;//
return 1.2;//
}
}
Code behind:
private void Button_Click(object sender,RoutedEventArgs e){
var ROC__ = MyClass.GetJson();
ROC__.Wait();
currency_.ROC = ROC__.Result;
}
What is does not work here means?
It is supposed to connect to URL and fetch the response and response should be assigned some value. Instead on debug with either step into or Continue the control exits the current line skips subsequent lines too.
(I have put debug on next lines too),The app just freezes.
I refereed similar codes for JSON parsing with HTTPClient on Stackoverflow and other blogs , its suggested to use System.Net.Http or Windows.Web.Http
Related Question : how-to-get-a-json-string-from-url
I think tasks are run and its going on forever wait mode , which seems strange as debug mode doesnt show code being run , it just show ready . There is no exception as well.
Am I doing something wrong or missing some Nuget reference?
Please suggest.
PS : Its the same case with httpClient.GetStringAsync method too.
On Console app this line works but not on UWP
var json = new WebClient().DownloadString("https://api.cryptonator.com/api/ticker/btc-usd");
Not duplicate of httpclient-getasync-never-returns-on-xamarin-android
Its not Xamarin , even though it is C# based , my code is different , its not WebApiClient or GetInformationAsync method which I am concerned about.
There're several errors with the code specified that needs to be cured. First, mark your event handler async:
private async void Button_Click(object sender, RoutedEventArgs e)
Second, await GetJson and since this is an asynchronous method, it is a better convention to add the suffix "Async" to it's name; therefore, await GetJsonAsync:
currency_.ROC = await MyClass.GetJsonAsync();
Now, back to GetJsonAsync itself, ReadAsStringAsync and ToObjectAsync should be awaited too:
private static async Task<double> GetJsonAsync()
{
using (var httpClient = new HttpClient())
{
Uri uri = new Uri("https://api.cryptonator.com/api/ticker/btc-usd");
HttpResponseMessage response = await httpClient.GetAsync(uri);
string result = await response.Content.ReadAsStringAsync();
//Below code is not relevent since code is failing on await
ExchangeRate exchangeRateObj = await Json.ToObjectAsync<ExchangeRate>(result);
return 1.2;//
}
}
The reason it wasn't working before is that there was a deadlock caused by the context switch between the await call and the synchronous block of .Wait(). Find more about that here.
Your code works fine. I regenerated it here below. Just get rid of that wonky wait call you're doing.
Do this. Create a new uwp app, paste in the below code and put a breakpoint on the return and see that it gets executed.
It will work regardless if you make your button handler async or not. If you don't make it asnyc though then the request won't be executed asynchronously
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SomeClass.GetJson();
}
}
public class SomeClass
{
public static async Task<double> GetJson()
{
using (var httpClient = new HttpClient())
{
Uri uri = new Uri("https://api.cryptonator.com/api/ticker/btc-usd");
HttpResponseMessage response = await httpClient.GetAsync(uri);
return 1.2;
}
}
}
Might I take this moment for a shameless plug of my UWP lib. It does this work for you.
https://github.com/DotNetRussell/UWPLibrary
In the BasecodeRequestManager.cs file there's a BeginRequest function that does this work async for you. The lib has many many other features as well.
So I tried this by myself. Unfortunately your informations where not complete so a little header:
For the Json-Handling I used Newtonsoft, because I didnt found the Json.ToObjectAsync in my UWP environment.
To create the ExchangeRate- class I used Json2CSharp.
Here are the ExchangeRate classes:
public class ExchangeRate
{
public string Error { get; set; }
public bool Success { get; set; }
public Ticker Ticker { get; set; }
public int Timestamp { get; set; }
}
public class Ticker
{
public string #Base { get; set; }
public string Change { get; set; }
public string Price { get; set; }
public string Target { get; set; }
public string Volume { get; set; }
}
I changed the Button_Click-Method to an async void. Normally its not recommend to have an async void instead of a async Task. But because it is a Handler from a UI-Element its not a problem, because the source will not wait anyway and you shouldn't call this methode directly from your code-behind.
The Button_Click-Method:
private async void Button_Click(object sender, RoutedEventArgs e)
{
var ROC__ = await MyClass.GetJson();
//Do whatever you want with the result.
//Its a double, maybe you want to return an ExchangeRate objcet insted
}
Inside of your GetJson-Method, you need to add an await for your async operations, or add the .Wait() directly after the method and not in a new line. You need to do this, because the Task automatically starts to run, when you call the async operation and the .Wait() comes to late. So your GetJson-Method looks like this:
public static async Task<Double> GetJson()
{
using (var httpClient = new HttpClient())
{
Uri uri = new Uri("https://api.cryptonator.com/api/ticker/btc-usd");
HttpResponseMessage response = await httpClient.GetAsync(uri);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var result = await response.Content.ReadAsStringAsync();
ExchangeRate rate = JsonConvert.DeserializeObject<ExchangeRate>(result); //Newtonsoft
return 1.2;
}
else
{
return -1; //Example value
}
}
}
In addition I added a check, if the Request was successful, to be sure, that we have a response. How I said before: I think you should return a ExchangeRate-object instead of a double, but this depends on your context.

Async call inside synchronous webservice

I have a web service which calls an external web service. The external service responds after 3-4 seconds.
Right now all the calls are synchronous, but does it make sense to use async calls instead (sample below)?
Will it help with performance (not keeping threads blocked)? Isn't the thread blocked on the first line of GetData()?
Thank you.
public class MyService : WebService
{
[WebMethod]
public string GetData()
{
string response = ExecuteRequest(externalUrl, someContent).Result;
return response;
}
private async Task<string> ExecuteRequest(string url, string content)
{
var httpResponse = await new HttpClient().PostAsync(url, new StringContent(content));
string responseStr = await httpResponse.Content.ReadAsStringAsync();
return responseStr;
}
}
To answer your question: Yes, it does make sense to use async calls instead, but your example is not async. If you wanted to make it async you'd have to do something like this:
public class MyService : WebService
{
[WebMethod]
public async Task<string> GetData()
{
string response = await ExecuteRequest(externalUrl, someContent);
return response;
}
private async Task<string> ExecuteRequest(string url, string content)
{
var httpResponse = await new HttpClient().PostAsync(url, new StringContent(content));
string responseStr = await httpResponse.Content.ReadAsStringAsync();
return responseStr;
}
}

Deadlock when calling async methods on Windows Phone 8

My problem is the following:
On the UI thread I have a button event, from where I call a service method:
private async void RefreshObjectsButton_Click(object sender, EventArgs e)
{
var objectService = new ObjectService();
var objects = await objectService.GetObjects(UserInfo.Token);
}
The service method is:
public class ObjectService : ServiceClientBase, IObjectService
{
public async Task<ObservableCollection<ObjectViewModel>> GetObjects(string token)
{
var response = await GetAsync<ObservableCollection<HookViewModel>>("uri_address", token);
return response;
}
}
And the GetAsync method which is implemented in ServiceClientBase:
public async Task<T> GetAsync<T>(string uri, string token)
{
using (var client = CreateClient())
{
try
{
HttpResponseMessage response = new HttpResponseMessage();
response = await client.GetAsync(uri);
T retVal = default(T);
if (response.IsSuccessStatusCode)
{
retVal = await response.Content.ReadAsAsync<T>();
}
return retVal;
}
catch (Exception ex)
{
//TO DO log exception
return default(T);
}
}
}
When execution reaches GetAsync<T>(), the request is sent, I get a result and retVal contains a list of values. However after GetAsync<T>() execution ends, GetObjects() will not continue its execution anymore. I believe it's a deadlock as explained here. However using the advices seen in the previous link didn't resolve my issue. It's clearly I'm missing something here.
Can someone explain why is this deadlock happening, and maybe provide some further advice in resolving this issue?

WebClient GET/POST without blocking UI

I would like to make non blocking GET and POST requests. I've managed to solve it with BackgroundWorker, but I need your help to achieve it using tasks.
public Task<string> Post(Uri uri, string data)
{
return _webClient.UploadStringTaskAsync(uri, data);
}
public Task<string> Get(Uri uri)
{
return _webClient.DownloadStringTaskAsync(uri);
}
I need the requests to run sequentially. What's the proper way to implement this? Flag methods with async and await them? Wait for each task using Task.WaitAll()?
An example of what I'm after:
Task<string> loginTask = Post("login", data);
// wait for webrequest to complete and make use of response string
// use data from first request in a new request:
Task<string> someOtherRequest = Get("details");
You can use await Task.Run():
await Task.Run(()=> { Run_UI_Blocking_Function(); });
For example:
In the Post() function, you can add this:
await Task.Run(()=> { PostString = Post(new Uri("url"), "data"); });
And in the 'Get()' function, you can add this:
await Task.Run(()=> { GetString = Get(new Uri("url")); });
You'd just need to add async to your calling function. Here's a completed example:
private async void btn_PostData_Click(object sender, EventArgs e)
{
await Task.Run(()=> { PostString = Post(new Uri("url"), "data"); });
}
Hope this helps. It's really short and sweet.
You need to mark the method as async, and await the result.
public async Task<string> Get(Uri uri)
{
return await _webClient.DownloadStringTaskAsync(uri);
}
You can also install the Microsoft HttpClient library, which is already plumbed for async calls and may be a little lighter weight.

Categories