2016-03-25 12 views
1

非同期メソッドの結果をUIに伝播するのに問題があります。async PointCollectionを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> 

ビューモデル

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;   
} 

問題は、DependencyObjectと同じスレッドでDependencySourceを作成する必要があることを示すSystem.ArgumentExceptionを引き起こすOnPropertyChangeにあります。私はDispatchersで何時間も過ごしましたが、それでもなお良いことはありません。

+0

_ "DependencyObjectと同じスレッドでDependencySourceを作成する必要があることを私に伝える" _ - これはあなたが行うことです。技術的には、それはあなたの質問を標準的な "ディスパッチャの使用"答えの複製にします。あなたの質問はあまりにも漠然としているので、それがあなたが必要とするすべての助けだと確かめることはできません。 'Task'ベースのメソッドでは、結果を'待つ 'ことができ、 'Dispatcher'オブジェクトとの明示的なやりとりを避けることができます。しかし、上のコード例では、 'Process()'がどのように呼び出されているかを示していません。問題を確実に再現する良い[mcve]を提供してください。 –

+0

ありがとうございます。上記の例を更新しましたが、他に何を置くべきかはわかりません。 – Tomas

+0

私が前のコメントで提供したリンクの記事は、あなたが知っておくべき情報のすべてを提供しています。 –

答えて

0

主な問題は、PointCollectionDependencyObjectであり、それを作成するスレッドが所有していることです。通常、このようなオブジェクトは他のスレッドでは使用できません。オブジェクトを所有するUIスレッドではないため、Dispatcher(明示的にまたは暗黙的にawaitをコード例として使用します)を使用すると、ここでは役に立ちません。実際、問題を引き起こすUIスレッドでそのオブジェクトを使用しようとしています。

DependencyObjectsの場合はFreezableオブジェクトである「スレッド間の共有なし」ルールの場合は重要な例外があります。PointCollectionです。ほかのスレッドがスレッドにアクセスしようとする前に所有スレッドでオブジェクトをフリーズすると、他のスレッドでも安全に使用できます。

は、だから、このように見えるためにあなたの 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にコピーできます。

+0

多くのありがとうピーター、コレクションを凍結するトリックでした。 – Tomas

関連する問題