#c# #wpf #asynchronous #async-await #task
#c# #wpf #асинхронный #async-ожидание #задача
Вопрос:
Я ищу несколько советов о том, как разобраться с Task
s в WPF, и мне было интересно, может ли кто-нибудь просмотреть мой код и указать, что я делаю неправильно?
По сути, приложение берет почтовый индекс из пользовательского интерфейса, который используется для создания экземпляра Task
сервиса, который получит долготу / широту, к которым можно получить доступ через экземпляр для использования в другом сервисе или самом пользовательском интерфейсе.
Я думаю, что у меня может быть состояние гонки, которое я хочу исправить, поскольку, когда ResultTextBlock.Text
установлено, оно равно нулю, но при создании экземпляра я вижу, что эти значения установлены.
Мы были бы весьма признательны за любые советы по Task
реализации и подключению.
Сервисный код
class PostcodeService
{
string _resu<
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();
}
}
Код пользовательского интерфейса
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();
}
}
Комментарии:
1. Это могло бы лучше подойти для CodeReview?
2. Насколько я понимаю, CodeReview предназначен для кода, который работает.
Ответ №1:
Ваш GetLongLatAsync()
метод должен возвращать 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 resu<
}
}
… и вы должны просто ожидать GetLongLatAsync()
в пользовательском интерфейсе:
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();
}
}
Вам не нужно использовать TaskCompletionSource
или запускать новый Task
для вызова асинхронного метода.
Комментарии:
1. Спасибо, у меня было подозрение, что перенос задач был ненужным.
2. О, прошу прощения — Закрыто!
Ответ №2:
Обработчики событий позволяют использовать async void
Ссылка на Async / Await — Лучшие практики в асинхронном программировании
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();
}
}
Во-вторых, чтобы избежать исчерпания потока, создайте один HttpClient
вместо того, чтобы создавать и удалять его, когда вам нужно выполнить это действие
Ссылка Вы неправильно используете HttpClient
Дизайн сервиса должен быть переработан в отдельные задачи
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 resu<
}
return null;
}
}
Обработчик событий теперь можно реорганизовать в
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
}
}
}
Комментарии:
1. Также должен быть только один экземпляр
PostcodeService
2. @Ackdari согласен. Со статическим конструктором и httpclient это не имеет большого значения. Класс может быть абстрагирован, чтобы разрешить внедрение там, где это необходимо
3. Спасибо за ссылки и рефакторинг. Я, конечно, займусь реализацией статического HttpClient