.NET 4.5 からWPFに以下のメソッドが追加されました。
public static void EnableCollectionSynchronization( IEnumerable collection, Object lockObject ) public static void EnableCollectionSynchronization( IEnumerable collection, Object context, CollectionSynchronizationCallback synchronizationCallback )
名前のまんまなのですが、非UIスレッドからバインディングされているコレクションに
対して安全にアクセスできるようにしてくれるメソッドです。これまでは、WinFormsと
同じ要領でDispacherを利用してアクセスするコードが書いていました(WPFもWinFormsも
SwingもJavaFXもシングルスレッドモデル)が、これが大分楽になります。
やり方は、コレクションに対してアクセスする前に、上記のメソッドを呼んでおくことです。
また、Enableの逆のDisableするためのメソッドもあります。
public static void DisableCollectionSynchronization( IEnumerable collection )
以下、サンプルです。サンプルでは二つのボタンがあって
片方は、OFFで片方はONにして動作するようになってます。
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Button x:Name="btnOff" Content="EnableCollectionSync=OFF" HorizontalAlignment="Left" Margin="9,10,0,0" VerticalAlignment="Top" Height="44" Click="btnOff_Click"/> <Button x:Name="btnOn" Content="EnableCollectionSync=On" HorizontalAlignment="Left" Margin="194,10,0,0" VerticalAlignment="Top" Height="44" Click="btnOn_Click"/> <ListBox x:Name="lstResults" Margin="10,86,10,10" ItemsSource="{Binding DataSource}"/> </Grid> </Window>
namespace WpfApplication1 { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Data; public partial class MainWindow : Window { readonly object _lock = new object(); public MainWindow() { InitializeComponent(); DataContext = new MyDataContext(); } internal async void btnOff_Click(object sender, RoutedEventArgs e) { // // BindingOperations.EnableCollectionSynchronizationを設定せずに // 非同期でデータにアクセス. // var dataSource = (DataContext as MyDataContext).DataSource; BindingOperations.DisableCollectionSynchronization(dataSource); try { await CreateDataSourceAsync().ConfigureAwait(false); } catch (InvalidOperationException invalidOpEx) { MessageBox.Show(invalidOpEx.Message); } catch (NotSupportedException notSupportEx) { MessageBox.Show(notSupportEx.Message); } } internal async void btnOn_Click(object sender, RoutedEventArgs e) { // // BindingOperations.EnableCollectionSynchronizationを設定して // 非同期でデータにアクセス. // var dataSource = (DataContext as MyDataContext).DataSource; BindingOperations.EnableCollectionSynchronization(dataSource, _lock); await CreateDataSourceAsync().ConfigureAwait(false); } internal Task CreateDataSourceAsync() { var dataSource = (DataContext as MyDataContext).DataSource; return Task.Run(() => { dataSource.Clear(); for (int i = 0; i < 20; i++) { dataSource.Add( new Data { Id = i, Name = string.Format("Name-{0:00}", i) } ); } }); } } class MyDataContext { public MyDataContext() { DataSource = new ObservableCollection<Data>(); } public ObservableCollection<Data> DataSource { get; private set; } } class Data { public int Id { get; set; } public string Name { get; set; } public override string ToString() { return String.Format("Id={0} Name={1}", Id, Name); } } }
実行すると、OFFにしている方のボタンをクリックすると今まで通り
非UIスレッドでコレクションに対して普通にアクセスしているので
エラーとなりますが、ONにしている方はちゃんとデータが表示されます。
以下、参考にした情報です。
- http://okazuki.hatenablog.com/entry/20110916/1316153519
- http://okazuki.hatenablog.com/entry/20120520/1337503048
- http://d.hatena.ne.jp/hilapon/20130225/1361779314
- http://www.infoq.com/jp/news/2012/01/WPF-45-Collections
- http://www.jonathanantoine.com/2011/09/24/wpf-4-5-part-7-accessing-collections-on-non-ui-threads/
- http://10rem.net/blog/2012/01/16/wpf-45-observable-collection-cross-thread-change-notification
- http://msdn.microsoft.com/ja-jp/library/System.Windows.Data.BindingOperations(v=vs.110).aspx
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場