かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

WPF 4.5の新機能「複数スレッドからのコレクションの操作」

前に試してダメだ〜!と思ってた奴ですが、私のやり方がダメだったみたいです。

BindingOperationsクラスのEnableCollectionSynchronizationメソッドをコレクションに対して呼んでやる必要があったみたいです。ということでコードを書き直してお試し。

プロジェクトを作って、UIスレッド以外からObservableCollectionに要素を追加する処理を持ったViewModelクラスを作成しました。因みにMVVM Light Toolkitを参照に追加してます。

public class MainWindowViewModel
{
    public ObservableCollection<DateTimeHolder> Items { get; private set; }
    public RelayCommand StartAddItemCommand { get; private set; }
    public RelayCommand StopAddItemCommand { get; private set; }

    public MainWindowViewModel()
    {
        this.Items = new ObservableCollection<DateTimeHolder>();
        // Itemsに対して複数スレッドから要素の変更を行っても大丈夫なようにする
        BindingOperations.EnableCollectionSynchronization(this.Items, new object());

        bool isStop = false;
        this.StartAddItemCommand = new RelayCommand(() =>
        {
            // 別スレッドからコレクションにアイテムを追加する
            Task.Run(async () =>
            {
                while (!isStop)
                {
                    this.Items.Add(new DateTimeHolder());
                    await Task.Delay(1000);
                }
                isStop = false;
            });
        });

        this.StopAddItemCommand = new RelayCommand(() =>
        {
            // 追加を辞める
            isStop = true;
        });
    }


}

// 日時を保持するクラス
public class DateTimeHolder
{
    private DateTime value = DateTime.Now;
    public override string ToString()
    {
        return this.value.ToString("yyyy/MM/dd HH:mm:ss");
    }
}

そして、MainWindowで適当にListBoxやButtonとバインドしてやります。

<Window x:Class="ThreadSafeCollectionBindingTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:ThreadSafeCollectionBindingTest"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <l:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <Button Content="Start" MinWidth="75" Margin="2.5" Command="{Binding StartAddItemCommand, Mode=OneWay}" />
            <Button Content="Stop" MinWidth="75" Margin="2.5" Command="{Binding StopAddItemCommand, Mode=OneWay}" />
        </StackPanel>
        <ListBox x:Name="listBox" Grid.Row="1" ItemsSource="{Binding Items}"/>

    </Grid>
</Window>

実行してStartボタンを押すと、UIスレッド以外からコレクションに要素を追加してるのに例外が出ずに表示が更新されてることが確認できます。これで、DispatcherCollectionみたいなものを実装しなくて済みますね。