【问题标题】:WPF Best practice for async propertyWPF 异步属性的最佳实践
【发布时间】:2018-03-17 00:38:02
【问题描述】:

假设我有一个长时间运行的 Web API 调用(异步方法),它返回一个字符串。

这两种解决方案之间是否存在最佳实践,可以在 WPF 属性中显示结果而不阻塞 UI?或者还有别的吗?

注意:两种解决方案都不会冻结 UI,我已经查看了 How to call an async method from a getter or setter?Async property in c# 的帖子。

我的 Wep API

private async Task<string> GetAsyncProperty()
{
    string result = "Async Property Value";

    // Web api call...
    await Task.Delay(TimeSpan.FromSeconds(10));

    return result;
}

解决方案 A

XAML:

<TextBlock Text="{Binding Path=AsyncPropertyA, UpdateSourceTrigger=PropertyChanged}" />

视图模型:

public MyConstructor()
{
    Task task = SetAsyncPropertyA();
}

private async Task SetAsyncPropertyA()
{
    this.AsyncPropertyA = await GetAsyncProperty().ConfigureAwait(false);
}

解决方案 B

XAML:

<TextBlock Text="{Binding Path=AsyncPropertyB, UpdateSourceTrigger=PropertyChanged, IsAsync=True, FallbackValue='Loading B...'}" />

视图模型:

public string AsyncPropertyB
{
    get
    {
        return GetAsyncPropertyB();
    }
}

private string GetAsyncPropertyB()
{
    return Task.Run(() => GetAsyncProperty()).Result;
}

注意:在解决方案 B 中,我可以添加在解决方案 A 中不起作用的 FallbackValue,并可能在 Task.Run 的 ContinueWith 中添加一些其他 UI 更新。

【问题讨论】:

  • 请注意,在 Bindings 上设置 UpdateSourceTrigger=PropertyChanged 是没有意义的。它仅在实际更新其源属性的绑定中有效,即 TwoWay 或 OneWayToSource 绑定。
  • 在B中,从属性getter返回GetAsyncProperty().Result就足够了。
  • 谢谢!如果我想显示一个绑定到布尔值的微调器以了解更新何时完成,我可以在没有Task.Run(...).ContinueWith(x =&gt; IsLoading = false... 的情况下做到吗?
  • .Result 是阻塞的,可能导致 WPF/UI 环境中的死锁。不要使用 .Result 或 .Wait!
  • @PeterBons 如果我在 XAML 中使用 IsAsync=True 则不会。即使我使用了IsAsync,是否还有可能导致死锁?

标签: c# wpf asynchronous data-binding async-await


【解决方案1】:

在这两种情况下,您都没有发现在尝试调用 Web API 时可能发生的任何错误。您可能希望将其记录到文件中和/或向用户显示错误消息。

在这种情况下,await 让事情变得简单——你可以使用 try/catch:

public MyConstructor()
{
    try
    {
        Task task = SetAsyncPropertyA();
    }
    catch (Exception e)
    {
        // log the error or show a message
    }
}

private async Task SetAsyncPropertyA()
{
    this.AsyncPropertyA = await GetAsyncProperty().ConfigureAwait(false);
}

您也可以将 try/catch 移至异步方法。在这种情况下,由于没有机会从中逃脱错误,因此您可以将其设置为 async void。有时这对于事件处理程序是必要的(至少在 Windows 窗体中 - 不确定 WPF。)

public MyConstructor()
{
    SetAsyncPropertyA();
}

private async void SetAsyncPropertyA()
{
    try
    {
        this.AsyncPropertyA = await GetAsyncProperty().ConfigureAwait(false);
    }
    catch (Exception e)
    {
        // log the error or show a message
    }
}

【讨论】:

    【解决方案2】:

    你应该尝试为此使用一个好的框架,它已经安装了那个轮子。

    看看ReactiveUI command sample

    LoadTweetsCommand = ReactiveCommand.CreateAsyncTask(() => LoadTweets())
    
    LoadTweetsCommand.ToProperty(this, x => x.TheTweets, out theTweets);
    LoadTweetsCommand.ThrownExceptions.Subscribe(ex => /* handle exception here */);
    

    这些扩展适用于 IObservable,它本身就是非常强大的工具:

    Observable.FromAsync(async () =>
                {
                    await Task.Delay(100);
                    return 5;
                }).ToProperty(x => )
    

    【讨论】:

    • 谢谢!我不知道这个框架,我将看看我的个人使用。但是我和我公司不信任互联网上的每个人,即使它是一个强大的框架,从你的话来说......目前在 GitHub 上只有 3100 颗星,我不知道 5 年后是否会有支持和更新.
    • 信任?它是开源的。目前它做得很好,所以你害怕哪里?请不要试图重新发明轮子。它会花费你更多,并且可能会不太稳定。
    • 然后关注可观察对象本身,它们是 .NET 的一部分。反应式编程非常简洁:)
    猜你喜欢
    • 2010-10-09
    • 1970-01-01
    • 2011-08-12
    • 2013-02-02
    • 1970-01-01
    • 2018-12-26
    • 1970-01-01
    • 1970-01-01
    • 2018-11-24
    相关资源
    最近更新 更多