MVVM和异步属性

3

我有一个ViewModel,其中包含以下属性:

public Employee SelectedEmployee
        {
            get { return _selectedEmployee; }
            set
            {
                if (value == _selectedEmployee) return;
                _selectedEmployee = value;

                if (_selectedEmployee != null)
                {
                    StaffHolidaysViewModel.HolidayAllowance = _staffDataService.EmployeeHolidayAllowance(_selectedEmployee.Id);
                    FireEmployeeSelectedMessage(SelectedEmployee.Id);
                }

                RaisePropertyChanged();
                RaisePropertyChanged(nameof(Allowance));
                RaisePropertyChanged(nameof(Taken));
                RaisePropertyChanged(nameof(Remaining));
                RaisePropertyChanged(nameof(TotalAbsences));
                RaisePropertyChanged(nameof(TotalSick));
                RaisePropertyChanged(nameof(TotalNonSickAbsences));
                RaisePropertyChanged(nameof(SelectedEmployeeLeavingDate));
                UpdateCanExecuteChanged();
            }
        }

这条线

StaffHolidaysViewModel.HolidayAllowance = _staffDataService.EmployeeHolidayAllowance(_selectedEmployee.Id);

这段代码包含一个我想要异步处理的方法调用。因为SelectedEmployee是一个绑定属性,所以我不知道该如何实现异步操作。

<ComboBox Name="StaffMembers" ItemsSource="{Binding FilteredEmployees}" SelectedItem="{Binding SelectedEmployee}" Width="200" BorderThickness="1" BorderBrush="DimGray">
                    <ComboBox.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Name}"/>
                        </DataTemplate>
                    </ComboBox.ItemTemplate>
                </ComboBox>

由于它是一个属性,我无法将其类型设置为Task<Employee>

在异步绑定MVVM属性方面,有什么推荐的方法吗?还是说这不可能实现?


5
我的理解是这是不可能的。绑定机制要求属性立即返回一个值。然而,有一些模式可以间接处理它。请参考Stephen Cleary在MSDN上的文章:https://msdn.microsoft.com/en-us/magazine/dn605875.aspx?f=255&MSPPError=-2147217396 - Bradley Uffner
2个回答

5

包含一个我想要异步化的方法调用。

我在async MVVM数据绑定上有一篇文章。基本上,我通常会创建一个可绑定的 Task - 我在文章中称之为 NotifyTaskCompletion,在我的更新库中缩短为 NotifyTask

您可以通过将 StaffHolidaysViewModel.HolidayAllowance 的类型从任何类型(我将其称为 THolidayAllowance)更改为 NotifyTask<T> 包装器(例如,NotifyTask<THolidayAllowance>)来使用它。

然后,您可以同步设置它:

StaffHolidaysViewModel.HolidayAllowance = NotifyTask.Create(
    _staffDataService.EmployeeHolidayAllowanceAsync(_selectedEmployee.Id));

您正在做的是启动EmployeeHolidayAllowanceAsync,然后将其TaskNotifyTask包装起来。这是同步完成的,因此可以在属性设置器中执行此操作。
然后,您还需要更新数据绑定。NotifyTask<T>有几个可用的属性。最明显的是Result,它最终将保存EmployeeHolidayAllowanceAsync的结果(在EmployeeHolidayAllowanceAsync完成之前,它返回默认值)。还有IsNotCompletedIsFaulted,您将想使用它们来向用户指示数据尚未到达或存在某些错误。

3

从技术上讲,让一个属性执行异步任务并不是理想的方法,因为它们需要返回一个值。

为什么? 让我们从另一个角度来看:

异步意味着UI线程不会停止(处理时UI不会挂起),而主要处理线程将等待操作完成并获取结果(考虑您是否要使用await)。

当您定义绑定时,UI直接依赖于使用await的属性来获取数据,这意味着UI将必须等待(这不能发生在async操作中),因此属性无法成为async,因此您无法使用await

到目前为止我使用过的所有解决方法:

  1. 一个异步返回方法的属性叫做.Result。我已经尝试了它,但是它并不能提供最佳输出。
  2. 你可以让你的方法返回 IAsyncOperation<> 而不是 Task<>。这将使你可以调用 .GetResults() 方法来获取结果而不会冻结用户界面线程。更多信息请参见 这里。如果你没有任何后续结果获取操作要执行,就使用这个方法。
  3. 最后,这个选项是我使用最多的,我创建一个返回 Task<> 的异步方法。我在那里执行所有的操作并验证结果。一旦我获得了所需的结果,我只需要更新属性并调用 RaisePropertyChanged。这样,我可以处理其他视图处理或中间加载屏幕,而不必被限制于无法停止处理线程。

底线

使用我上面定义的三种方法中的任意一种。它们都很好。以下是考虑作为获取游戏服务器分数示例的用例的摘要:

  1. 如果您不想停止任何线程,只需定义属性的数据源,请使用IAsyncOperation<>。代码处理将不会停止或等待进程获取结果。例如:您只想获取分数并显示它。
  2. 如果您希望代码等待任务完成后执行其他操作,请使用返回Task<>的方法。例如:您希望在计算出分数后将整数值转换为百分比。
  3. 使用.Result,但它从未对我起作用过。

如果还有其他问题,请在评论中提出。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接