Asynchronous 如何从属性设置器调用异步函数?

Asynchronous 如何从属性设置器调用异步函数?,asynchronous,mvvm,Asynchronous,Mvvm,我有一个wpf应用程序,它的TextBox绑定到虚拟机中的actualpaginumber属性。我还有一个DataGrid绑定到显示给定页面的ObservableCollection。数据存储在数据库中。当我更改ActualPageNumber时,setter访问数据库,这可能会很慢。这就是为什么我需要一个异步setter,以保持gui的响应性 我知道没有异步设置程序: 我还发现了一些有用的东西,比如 我仍然在为如何继续处理这个案子而挣扎。AsyncEx库可以是解决方案,举个例子就好了 我只想通

我有一个wpf应用程序,它的
TextBox
绑定到虚拟机中的
actualpaginumber
属性。我还有一个
DataGrid
绑定到显示给定页面的
ObservableCollection
。数据存储在数据库中。当我更改
ActualPageNumber
时,setter访问数据库,这可能会很慢。这就是为什么我需要一个异步setter,以保持gui的响应性

我知道没有异步设置程序:

我还发现了一些有用的东西,比如

我仍然在为如何继续处理这个案子而挣扎。AsyncEx库可以是解决方案,举个例子就好了

我只想通知用户页面实际上正在加载。如果我可以从setter调用async,我就可以这样做,但是我仍然不能在setter中使用
wait
,因为它不是
async

我还有一个DataGrid绑定到显示给定页面的ObservableCollection

这将是最困难的部分
DataGrid
(以及
DataTable
和friends)是使用同步API设计的,从未更新过以支持异步

我对
DataGrid
不是很熟悉,但我想说您的选择是:

  • 用您自己的自定义控件替换
    DataGrid
    ,例如,显示自定义控件的
    ListView
    。然后可以显示加载微调器,因为您可以控制自定义控件。这有一些常见的模式,如
    NotifyTask
  • 可能有一种方法可以通过异步加载的方式虚拟化
    DataGrid
    中的数据。我对DataGrid不太熟悉,不知道这是否真的可能,但值得研究一下
  • 1)对于
    DataGrid
    的响应,此绑定属性可能会有所帮助:
    IsAsync=True

    <DataGrid ItemsSource="{Binding MyCollection, IsAsync=True}"
    
    但是要小心,虚拟化可能会捉弄你。例如,我有一个RowHeader(带有行号),当虚拟化开启时,值被置乱

    2)关于数据绑定的异步setter:我使用的是自定义版本的
    IAsyncCommand
    (请参阅)

    我以两种方式使用该命令:a)从视图绑定到它(完全避免异步setter)或b)从setter启动它(不好)

    示例:我创建了一个
    UpdateCommand
    作为
    asynchcommand
    并异步放置了我需要做的所有事情(比如从数据库获取值)。此命令中的所有内容都包装在一个“正在进行”控件的显示+隐藏中,就像控件一样,在我的例子中,是一个带有微调器的透明盖子+“请稍候…”,以防止其他用户操作(“屏幕”在执行任务时可见)。剥离样品:

        ....
        public MainWindowViewModel()
        {
           UpdateCommand = AsyncCommand.Create(Update); // our own custom implementation of AsyncCommand
        }
        ....
    
        public AsyncCommand UpdateCommand { get; }
        internal async Task Update(object arg)
        {
            await SafeWrapWithWaitingScreenAsync(async () =>
            {
                var value = (int)arg; // or the ActualPageNumber, if used from a1)
    
                var data = await GetDataFromDb(value).ConfigureAwait(false);
    
                ...// fill in MyCollection (which is the DataGrid's ItemsSource) using the data
    
                OnPropertyChanged(nameof(MyCollection));// if still needed
            }).ConfigureAwait(false);
        }
    
        ....
        public async Task SafeWrapWithWaitingScreenAsync(Func<Task> action)
        {
            DisplayWaitingScreen = true; //Visibility of the "Waiting screen" binds to this
            try
            {
                await action().ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                HandleException(ex); // display/log ex
            }
            finally
            {
                DisplayWaitingScreen = false;
            }
        }
    
    b)不确定这是否正确,但在使用
    NotifyTaskCompletion
    (我将来可能会使用)查看setter之前,我启动了如下命令:

    private int actualPageNumber;
    public int ActualPageNumber
        {
            get => actualPageNumber;
            set
            {
                actualPageNumber = value;
                OnPropertyChanged(); //the sync way
    
                UpdateCommand.Execute(value);
            }
        }
    

    谢谢@Stephen显然我认为选项1将是答案。我试试看。我只是想知道,在WPF中,绑定属性设置器不能异步的原因是什么。在我的程序中,有几个属性设置程序应该启动DB操作(更改页面大小,更改过滤器)。如果我可以在这些设置器中简单地使用异步操作,我也可以等待它们,那就太好了。@IstvanHeckl:Property setter根据C语言规范返回
    void
    <TextBox Text="{Binding ActualPageNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <TextBox.InputBindings>
             <KeyBinding Key="Return" Command="{Binding UpdateCommand}" CommandParameter="{Binding ActualPageNumber}" />
             <KeyBinding Key="Enter" Command="{Binding UpdateCommand}" CommandParameter="{Binding ActualPageNumber}" />
        </TextBox.InputBindings>
    </TextBox>
    
    private int actualPageNumber;
    public int ActualPageNumber
        {
            get => actualPageNumber;
            set
            {
                actualPageNumber = value;
                OnPropertyChanged(); //the sync way
    
                UpdateCommand.Execute(value);
            }
        }