c# - 将异步PointCollection更改传播到UI

标签 c# asynchronous mvvm

我在将异步方法的结果传播到UI时遇到麻烦。

XAML

<Window x:Class="COVMin.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:COVMin"        
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid Margin="0,0,0,0">
        <Border BorderThickness="1" BorderBrush="Black" Background="White" Margin="4" VerticalAlignment="Top" Height="170">
        <Polygon Points="{Binding Points}" Stretch="Fill" Fill="Black" Opacity="0.8" />
    </Border>
    <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Height="24" Marin="0,0,0,10" VerticalAlignment="Bottom" Width="75" Command="{Binding DrawPointsCommand}"/>
    </Grid>

View 模型
class MainViewModel : ViewModelBase
{        
    private PointCollection points { get; set; }

    public PointCollection Points
    {
        get { return this.points; }
        set
        {                
            this.points = value;
            OnPropertyChanged("Points");
        }
    }

    public ICommand DrawPointsCommand { get; private set; }

    /// <summary>
    /// Simplified, in real it´s long time operation causing UI to freeze.
    /// </summary>        
    private Task<PointCollection> ConvertToPointCollection()
    {
        return Task.Run<PointCollection>(() =>
        {
            PointCollection points = new PointCollection();
            points.Add(new System.Windows.Point(0, 6236832));                
            points.Add(new System.Windows.Point(255, 6236832));

            return points;
        });
    }

    /// <summary>
    /// 
    /// </summary>
    private async Task<PointCollection> Process()
    {            
        this.Points = await ConvertToPointCollection();
        return this.Points;
    }

    /// <summary>
    /// Method calling long-time operation bound to button as a Command.
    /// </summary>
    private async void GetValues()
    {
        this.Points = await Process();            
    }

    /// <summary>
    /// Constructor.
    /// </summary>
    public MainViewModel()
    {
        this.DrawPointsCommand = new DelegateCommand(GetValues);
    }
}

ViewModelBase
    /// <summary>
    /// Base class for PropertyChanged event handling.
    /// </summary>        
    class ViewModelBase : INotifyPropertyChanged
    {  
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
    }

DelegateCommand类
public class DelegateCommand : ICommand
{        
    private readonly Action _action;

    public DelegateCommand(Action action)
    {
        _action = action;
    }

    public void Execute(object parameter)
    {
        _action();
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;        
}

问题出在OnPropertyChange上,这导致System.ArgumentException告诉我必须在与DependencyObject相同的线程上创建DependencySource。我已经花了很多时间尝试与Dispatchers一起工作,虽然不是,但是仍然没有好处。

最佳答案

主要问题是PointCollectionDependencyObject,因此由创建它的线程拥有。通常,您不能在其他线程中使用此类对象。在这里使用Dispatcher(显式地或隐式地使用await进行编码)(在您的代码示例中这样做)将无济于事,因为不是拥有该对象的UI线程。确实,它正在尝试在导致问题的UI线程上使用该对象。

尽管DependencyObjectsFreezable一样,也是PointCollection对象,但是“不跨线程共享”规则是一个重要的异常(exception)。如果在其他线程尝试访问该对象之前将其冻结在拥有的线程中,则可以安全地在其他线程中使用该对象。

因此,您可以将ConvertToPointCollection()方法更改为如下所示:

private Task<PointCollection> ConvertToPointCollection()
{
    return Task.Run<PointCollection>(() =>
    {
        PointCollection points = new PointCollection();
        points.Add(new System.Windows.Point(0, 6236832));                
        points.Add(new System.Windows.Point(255, 6236832));

        points.Freeze();

        return points;
    });
}

当然,这也将防止以后修改对象。如果需要修改集合,则必须采用其他方法。例如,在UI线程中创建PointCollection,然后使用中间类型(例如List<Point>)将新点从您的后台线程传递到UI线程,然后UI线程可以在其中将这些点复制到PointCollection中。

关于c# - 将异步PointCollection更改传播到UI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36223166/

相关文章:

c# - MVVM 从其他 ViewModel 在 ViewModel 上执行命令

javascript - 绑定(bind)可观察对象的子属性时未获取 View 中的所有值

wpf - TextBlock 文本的整数值 0 上的 DataTrigger 不起作用

c# - 改善绩效反射(reflect)——我应该考虑哪些替代方案?

c# - 引号内的C#变量

objective-c - 使用 NSURLConnection 串行发出异步请求

flutter - Flutter-如何将进度条赋予按钮的on:Pressed()函数?

c++ - 默认启动策略和std::launch::deferred之间的区别

c# - C#6.0默认初始化

c# - 从javascript重置C#变量的值