【问题标题】:Correctly implementing Tasks in a WPF application在 WPF 应用程序中正确实现任务
【发布时间】:2019-08-11 04:47:54
【问题描述】:

我正在寻找一些关于如何在 WPF 中处理 Tasks 的建议,并且想知道是否有人可以查看我的代码并指出我做错了什么?

基本上,该应用程序从 UI 中获取一个邮政编码,该邮政编码用于实例化 Task 服务,该服务将获得经度/纬度,可通过实例访问以用于其他服务或 UI 本身。

我想我可能有一个竞争条件,我希望纠正它,因为当设置 ResultTextBlock.Text 时它是零,但是通过实例化我看到了这些值设置。

任何关于Task 实施和接线的建议将不胜感激。

服务代码

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();
    }
}

界面代码

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();
    }
}

【问题讨论】:

  • 这可能更适合 CodeReview?
  • 我的理解是 CodeReview 是针对有效代码的。

标签: c# wpf asynchronous async-await task


【解决方案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 result;
    }
}

...您应该在 UI 中等待 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 来调用异步方法。

【讨论】:

  • 谢谢,我怀疑任务包装是不必要的。
  • 对不起 - 关闭!
【解决方案2】:

事件处理程序允许使用 async void

参考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();
    }
}

其次为避免线程耗尽,创建单个HttpClient,而不是在需要执行此操作时创建和处置它

参考You're using HttpClient wrong

服务的设计应该重构为单独的关注点

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;
    }
}

现在可以将事件处理程序重构为

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
        }

    }
}

【讨论】:

  • PostcodeService也应该只有一个实例
  • @Ackdari 同意了。使用静态构造函数和 httpclient 并不重要。可以抽象类以允许在需要的地方注入
  • 感谢链接和重构。我肯定会去实现一个静态 HttpClient
猜你喜欢
  • 2016-07-28
  • 2017-03-20
  • 2012-06-15
  • 1970-01-01
  • 2019-06-06
  • 2012-10-22
  • 2013-12-03
  • 1970-01-01
  • 2021-08-12
相关资源
最近更新 更多