WPF异步属性的最佳实践

时间:2017-10-05 11:47:01

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

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

这两个解决方案之间是否有最佳实践可以在不阻止UI的情况下在WPF属性中显示结果?或者还有另一个吗?

注意:这两个解决方案都没有冻结用户界面,我已经查看了帖子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更新。

2 个答案:

答案 0 :(得分:4)

你应该尝试使用一个好的框架,它已经投入了那个轮子。

看看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 => )

答案 1 :(得分:2)

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

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

Public Class daDbContext
    Inherits System.Data.Entity.DbContext

    Public Property MyTable() As DbSet(Of MyTable)

    Public Sub New(con As OracleConnection)
        MyBase.New(CType(con, DbConnection), contextOwnsConnection:=True)
    End Sub
End Class

您还可以将try / catch移动到异步方法。在这种情况下,因为没有错误从它逃脱,你可以使它异步无效。有时这对于事件处理程序是必要的(至少在Windows窗体中 - 不确定WPF。)

Try
    Dim con As New OracleConnection("Data Source=mydb;Password=mypassword;Persist Security Info=True;" + _
                                    "User ID=myuser;Connection Lifetime=0;Connection Timeout=15;HA Events=false;" + _
                                    "Load Balancing=false;Max Pool Size=100;Min Pool Size=1;Pooling=false;" + _
                                    "Validate Connection=false")

    con.Open()

    ' Try to read data with our connection'
    Dim com As OracleCommand = con.CreateCommand()
    com.CommandText = "SELECT * FROM mytable"
    Dim reader As OracleDataReader = com.ExecuteReader()
    While reader.Read()
        Console.WriteLine("new connection: " + DirectCast(reader("mycolumn"), String))
        ' Works fine..'
    End While

    ' Init ef-db-context'
    Dim ctx As New daDbContext(con)

    ' Try to read Data with connection from ef-db-context'
    com = DirectCast(ctx.Database.Connection.CreateCommand(), OracleCommand)
    com.CommandText = "SELECT * FROM mytable"
    reader = com.ExecuteReader()
    While reader.Read()
        Console.WriteLine("ctx connection: " + DirectCast(reader("mycolumn"), String))
        ' Works fine..'
    End While

    ' Throws exception'
    Dim l As List(Of MyTable) = ctx.MyTable.ToList()
    Console.WriteLine(l.First().MyColumn)
Catch ex As Exception
    Console.WriteLine(ex.Message)
End Try