I'm looking for some advice on getting to grips with Tasks in WPF and was wondering if anyone could have a look over my code and point out what I'm doing incorrectly?
Basically the application takes a postcode from the UI which is used to instantiate a Task service which will get Longitude/Latitude which can be accessed via the instance for use in another service or the UI itself.
I think I may have a race condition which I'm looking to correct as when ResultTextBlock.Text is set it's a zero, but stepping through instantiation I see those values set.
Any advice on Task implementation and wiring would be greatly appreciated.
Service Code
class PostcodeService
{
string _result;
string _postcode;
HttpResponseMessage _response;
RootObject rootObject;
public double Latitude { get; private set; }
public double Longitude { get; private set; }
public PostcodeService(string postcode)
{
this._postcode = postcode;
rootObject = new RootObject();
}
public async Task<string> GetLongLatAsync()
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("https://api.postcodes.io/postcodes/" + this._postcode);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
try
{
_response = await client.GetAsync(client.BaseAddress);
if(_response.IsSuccessStatusCode)
{
//cast result into model and then set long/lat properties which can then be used in the UI
_result = await _response.Content.ReadAsStringAsync();
rootObject = JsonConvert.DeserializeObject<RootObject>(_result);
Longitude = Double.Parse(rootObject.result.longitude.ToString());
Latitude = Double.Parse(rootObject.result.latitude.ToString());
}
}
catch(Exception ex)
{
ex.ToString();
}
}
TaskCompletionSource<string> tc = new TaskCompletionSource<string>(_result);
return tc.ToString();
}
}
UI Code
private void PostcodeButton_Click(object sender, RoutedEventArgs e)
{
_clearStatus();
if (_validatePostcode())
{
Task T1 = Task.Factory.StartNew(async () =>
{
// get long lat from api
_postcode = new PostcodeService(PostcodeTextBox.Text);
await _postcode.GetLongLatAsync();
});
//Race condition?
ResultTextBlock.Text = _postcode.Latitude.ToString();
}
}
Your GetLongLatAsync() method should return a string:
public async Task<string> GetLongLatAsync()
{
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri("https://api.postcodes.io/postcodes/" + this._postcode);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
_response = await client.GetAsync(client.BaseAddress);
string result = null;
if (_response.IsSuccessStatusCode)
{
//cast result into model and then set long/lat properties which can then be used in the UI
result = await _response.Content.ReadAsStringAsync();
rootObject = JsonConvert.DeserializeObject<RootObject>(_result);
Longitude = Double.Parse(rootObject.result.longitude.ToString());
Latitude = Double.Parse(rootObject.result.latitude.ToString());
}
return result;
}
}
...and you should simply await GetLongLatAsync() in the UI:
private async void PostcodeButton_Click(object sender, RoutedEventArgs e)
{
_clearStatus();
if (_validatePostcode())
{
// get long lat from api
_postcode = new PostcodeService(PostcodeTextBox.Text);
string result = await _postcode.GetLongLatAsync();
ResultTextBlock.Text = result.ToString();
}
}
You don't need to use a TaskCompletionSource nor start a new Task to call an async method.
Event handlers allow async void to be used
Reference Async/Await - Best Practices in Asynchronous Programming
private async void PostcodeButton_Click(object sender, RoutedEventArgs e) {
_clearStatus();
if (_validatePostcode()) {
// get long lat from api
_postcode = new PostcodeService(PostcodeTextBox.Text);
await _postcode.GetLongLatAsync(); //Offload UI thread
//Back on UI thread
ResultTextBlock.Text = _postcode.Latitude.ToString();
}
}
Secondly to avoid thread exhaustion, create a single HttpClient instead of creating and disposing it when you need to perform this action
Reference You're using HttpClient wrong
The design of the service should be refactored into separate concerns
public class Location {
public Location(double lat, double lon) {
Latitude = lat;
Longitude = lon;
}
public double Latitude { get; private set; }
public double Longitude { get; private set; }
}
public class PostcodeService {
private static Lazy<HttpClient> client;
static PostcodeService() {
client = new Lazy<HttpClient>(() => {
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://api.postcodes.io/postcodes/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
return httpClient;
});
}
public async Task<Location> GetLongLatAsync(string postcode) {}
var response = await client.Value.GetAsync(postcode);
if(response.IsSuccessStatusCode) {
//cast result into model and then set long/lat properties which can then be used in the UI
var rootObject = await response.Content.ReadAsAsync<RootObject>();
var Longitude = Double.Parse(rootObject.result.longitude.ToString());
var Latitude = Double.Parse(rootObject.result.latitude.ToString());
var result = new Location(Latitude, Longitude);
return result;
}
return null;
}
}
The event handler can now be refactored to
private async void PostcodeButton_Click(object sender, RoutedEventArgs e) {
_clearStatus();
if (_validatePostcode()) {
// get long lat from api
var service = new PostcodeService();
var location = await service.GetLongLatAsync(PostcodeTextBox.Text); //Offload UI thread
//Back on UI thread
if(location != null) {
ResultTextBlock.Text = location.Latitude.ToString();
} else {
//Some message here
}
}
}
Related
I wrote two views in my application, and when I am changin from "Home" to "Details" it causes lag for about 0.5 - 1 sec. I understand, that the reason are my API calls in the constructor, but I added threading, and first filling in an empty properties, and after threads finish it should raise a OnPropertyChange. My "Details" viewmodel is here
public class DetailsViewModel : ObservableObject
{
private ObservableCollection<Market> _markets;
private Asset _asset;
private readonly Uri _baseUri = new Uri("https://cryptingup.com/api/assets/");
public RestClient Client { get; set; }
public ObservableCollection<Market> Markets
{
get
{
return _markets;
}
set
{
_markets = value;
OnPropertyChanged();
}
}
public Asset Asset
{
get
{
return _asset;
}
set
{
_asset = value;
OnPropertyChanged();
}
}
public DetailsViewModel(string id)
{
Client = new RestClient();
Markets = new ObservableCollection<Market>();
new Thread(() =>
{
RequestCurrencyDetails(id);
})
{
IsBackground = true
}.Start();
RequestAllMarkets(id);
new Thread(() =>
{
RequestAllMarkets(id);
})
{
IsBackground = true
}.Start();
}
private void RequestCurrencyDetails(string id)
{
Uri uri = new Uri(_baseUri, id);
var request = new RestRequest(uri.ToString());
request.AddHeader("Accept", "application/json");
request.AddHeader("Content-Type", "application/json");
var response = Client.GetAsync(request).GetAwaiter().GetResult();
var curemodel = JsonConvert.DeserializeObject<CurrencyModelCU>(response.Content);
Asset = curemodel.Asset;
}
private void RequestAllMarkets(string id)
{
string marketPath = id + "/markets";
Uri uri = new Uri(_baseUri, marketPath);
var request = new RestRequest(uri.ToString());
request.AddHeader("Accept", "application/json");
request.AddHeader("Content-Type", "application/json");
var response = Client.GetAsync(request).GetAwaiter().GetResult();
var marmodel = JsonConvert.DeserializeObject<MarketsModel>(response.Content);
ObservableCollection<Market> temp = new ObservableCollection<Market>();
for (int i = 0; i < 10; i++)
{
temp.Add(marmodel.Markets[i]);
}
Markets = temp;
}
}
I know, that I did something wrong with threading here, but I can't get the reason by myself. Thanks for answer
I highly recommend using await async instead of Thread. Also RequestAllMarkets(id); is called synchronously. this is most likley causing the "lag".
Here is a sample how to use Task.Run to "fire and forget the loading operation.
https://dotnetfiddle.net/sK3ivq
using System;
using System.Threading;
using System.Diagnostics;
using System.Threading.Tasks;
public class Program
{
public static void Main()
{
Console.WriteLine("start");
Stopwatch sw = new Stopwatch();
sw.Start();
DetailsViewModel vm = new DetailsViewModel();
Console.WriteLine($"already back from ctor -> no lag. prove: {sw.ElapsedMilliseconds}ms foo:{vm.Foo}");
// we use thread sleep -> in wpf app you don't have to wait because property change took place in VM.
Thread.Sleep(1000);
Console.WriteLine($"we waited long enough...: {sw.ElapsedMilliseconds}ms foo:{vm.Foo}");
}
public class DetailsViewModel
{
public DetailsViewModel()
{
Foo= "not initialized";
Task.Run(()=> Init());
}
public async Task Init()
{
await Task.Delay(500);
Foo = "bar";
}
public string Foo {get;set;}
}
}
I have an API call using client.GetAsync(url) within a SSIS script task but for some reason its not waiting for response from API and jumping back to the entry point for the script task which is public void Main(). Done some reading and found out that it might result in a deadlock for some reason but tried all the variations I could find to get it to work but with no luck. Something else that I don't understand is the exact same code is running on a webpage and that works perfect and waits for response from the api and continuing the flow.
Script Task entry point
The response here for payload is: ID =5, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
Here if in debug mode and moving the process back to go through the process again I noticed there 2 threads one current executing and the old one with the response I was expecting on the first call but not too sure what this means.
public void Main() {
// TODO: Add your code here
try {
PackageDetails packageInfo = new PackageDetails {
PackageNumber = 1234567891, Env = "Development", UserName = "USER"
};
var payload = API.ListHeadByPackAsync(packageInfo);
//var test = GetResponse();
Dts.TaskResult = (int) ScriptResults.Success;
} catch (Exception ex) {
System.Console.Write(ex.Message);
Dts.TaskResult = (int) ScriptResults.Failure;
}
}
API Call
public static class API {
public static async Task<PackageDetails> ListHeadByPackAsync(PackageDetails package) {
PackageDetails packageInfo = new PackageDetails();
try {
using(var client = new ApiClient(requestUrl, authToken)) {
var response = await client.GetAsync(); //-> not waiting for response
}
} catch (Exception err) {
switch (err.Message) {
//TODO:
}
}
return packageInfo;
}
}
Client
public class ApiClient: IDisposable {
private readonly TimeSpan _timeout;
private HttpClient _httpClient;
private HttpClientHandler _httpClientHandler;
private readonly string _baseUrl;
private readonly string _credentials;
//private const string MediaTypeXml = "application/csv";
public ApiClient(string baseUrl, string authToken, TimeSpan ? timeout = null) {
_baseUrl = baseUrl;
_credentials = Base64Encode(authToken);
_timeout = timeout ?? TimeSpan.FromSeconds(90);
}
public async Task < string > GetAsync() {
EnsureHttpClientCreated();
using(var response = await _httpClient.GetAsync(_baseUrl).ConfigureAwait(continueOnCapturedContext: false))
//-> after executing above line it will go straight to public void Main(), dose not wait for response
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public void Dispose() {
_httpClientHandler?.Dispose();
_httpClient?.Dispose();
}
private void CreateHttpClient() {
_httpClientHandler = new HttpClientHandler {
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
};
_httpClient = new HttpClient(_httpClientHandler, false) {
Timeout = _timeout
};
if (!string.IsNullOrWhiteSpace(_baseUrl)) {
_httpClient.BaseAddress = new Uri(_baseUrl);
}
_httpClient.DefaultRequestHeaders.Add("Authorization", "Basic" + " " + _credentials);
}
private void EnsureHttpClientCreated() {
if (_httpClient == null) {
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
CreateHttpClient();
}
}
public static string Base64Encode(string token) {
var tokenBytes = System.Text.Encoding.UTF8.GetBytes(token);
return System.Convert.ToBase64String(tokenBytes);
}
}
I tried make it as you suggested (answer below this). Unfortunately still I have this problem.
This is my new TokenService.cs:
class TokenService
{
TokenKeeper tokenkeeper;
public TokenService()
{
tokenkeeper = new TokenKeeper();
}
public async void AwaitGetToken()
{
tokenkeeper = new TokenKeeper();
tokenkeeper = await GetToken();
}
private async Task<TokenKeeper> GetToken()
{
string stringuri = string.Format("{0}/token", RestAccess.HostUrl);
var dict = new Dictionary<string, string>();
dict.Add("grant_type", "password");
dict.Add("username", RestAccess.User);
dict.Add("password", RestAccess.Pass);
dict.Add("client_id", RestAccess.Client_ID);
dict.Add("client_secret", RestAccess.Client_secret);
tokenkeeper=new TokenKeeper();
var httpclient = new HttpClient();
try
{
var req = new HttpRequestMessage(HttpMethod.Post, stringuri) { Content = new FormUrlEncodedContent(dict) };
var res = await httpclient.SendAsync(req);
if (res.IsSuccessStatusCode)
{
var content = await res.Content.ReadAsStringAsync();
tokenkeeper = JsonConvert.DeserializeObject<TokenKeeper>(content);
return tokenkeeper;
}
return tokenkeeper;
}
catch
{
return tokenkeeper;
}
finally
{
httpclient.CancelPendingRequests();
httpclient.Dispose();
}
}
}
My tokenkeeper is simple class:
public class TokenKeeper
{
public string access_token { get; set; }
public string refresh_token { get; set; }
public TokenKeeper()
{
access_token = "";
refresh_token = "";
}
}
In my calling code I have as below:
...
tokenservice.AwaitGetToken();
GetXFMapUserFromAzureAndSetVariable(RestAccess.User);
_navigationService.NavigateAsync("ZleceniaListContentPage", par);
...
tokenservice.AwaitGetToken() and GEtXFMapUserFromAzureAndSetVariable(RestAccess.User) they are similar. Both await but tokenservice.AwaitGetToken() is POST and GEtXFMapUserFromAzureAndSetVariable is GET.
GEtXFMapUserFromAzureAndSetVariable is working correctly but if I call tokenservice.AwaitGetToken() the aplication get the token but before it's continue. At the end the instruction GEtXFMapUserFromAzureAndSetVariable it is being done tokenservice.AwaitGetToken() responds.
How can I wait with call GEtXFMapUserFromAzureAndSetVariable until I receive a reply from tokenservice.AwaitGetToken() ???
Delete your AwaitGetToken method, it is useless.
Now make you GetToken method public and call it with await:
await tokenService.GetToken();
await GetXFMapUserFromAzureAndSetVariable(RestAccess.User);
await _navigationService.NavigateAsync("ZleceniaListContentPage", par);
Basically you always need to await methods returning Task, or it will run in a parallel fashion, and you will lose consistency in your code.
You seem very confused by Task and async/await, I will strongly advise you to read tutorials and docs on it:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
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
Up until now, I've been making synchronous HttpWebRequest calls in WinForms applications. I want to start doing it asynchronously so as to not block the UI thread and have it hang. Therefore, I am attempting to switch to HttpClient, but I am also new to async and tasks and don't quite get it, yet.
I can launch the request and get a response and isolate the data I want (result, reasonPhrase, headers, code) but don't know how to get that back for display in textBox1. I also need to capture ex.message and return to the form if a timeout or cannot connect message occurs.
Every single example I see has the values written to Console.WriteLine() at the point they are available, but I need them returned back to the form for display and processing and have a hard time understanding how.
Here's a simple example:
namespace AsyncHttpClientTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
textBox1.Text = "calling Test()...\r\n";
DownloadPageAsync();
// need to get from DownloadPageAsync here: result, reasonPhrase, headers, code
textBox1.AppendText("done Test()\r\n");
}
static async void DownloadPageAsync()
{
// ... Use HttpClient.
using (HttpClient client = new HttpClient())
{
try
{
using (HttpResponseMessage response = await client.GetAsync(new Uri("http://192.168.2.70/")))
{
using (HttpContent content = response.Content)
{
// need these to return to Form for display
string result = await content.ReadAsStringAsync();
string reasonPhrase = response.ReasonPhrase;
HttpResponseHeaders headers = response.Headers;
HttpStatusCode code = response.StatusCode;
}
}
}
catch (Exception ex)
{
// need to return ex.message for display.
}
}
}
}
}
Any helpful hints or advice?
create a model to hold the data you want to return
public class DownloadPageAsyncResult {
public string result { get; set; }
public string reasonPhrase { get; set; }
public HttpResponseHeaders headers { get; set; }
public HttpStatusCode code { get; set; }
public string errorMessage { get; set; }
}
Avoid using async void methods. Convert the method to async Task and call it in the event handler where it is allowed.
private async void button1_Click(object sender, EventArgs e) {
textBox1.Text = "calling Test()...\r\n";
var result = await DownloadPageAsync();
// Access result, reasonPhrase, headers, code here
textBox1.AppendText("done Test()\r\n");
}
static HttpClient client = new HttpClient();
static async Task<DownloadPageAsyncResult> DownloadPageAsync() {
var result = new DownloadPageAsyncResult();
try {
using (HttpResponseMessage response = await client.GetAsync(new Uri("http://192.168.2.70/"))) {
using (HttpContent content = response.Content) {
// need these to return to Form for display
string resultString = await content.ReadAsStringAsync();
string reasonPhrase = response.ReasonPhrase;
HttpResponseHeaders headers = response.Headers;
HttpStatusCode code = response.StatusCode;
result.result = resultString;
result.reasonPhrase = reasonPhrase;
result.headers = headers;
result.code = code;
}
}
} catch (Exception ex) {
// need to return ex.message for display.
result.errorMessage = ex.Message;
}
return result;
}
The HttpClientshould also not be created every time the download is called.
Refer to What is the overhead of creating a new HttpClient per call in a WebAPI client?