Правильная реализация задач в приложении WPF

#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