RunningCSharp

MS系開発者による、雑多な記事。記事は所属企業とは関係のない、個人の見解です。

Xamarin:ItemsControl.ItemsSourceプロパティとバインドした配列を別スレッドから動かす

WPFでもあった問題なのですが、(async/awaitなどを用いるなどして)メインではないスレッドから、ItemsControlのItemsSourceプロパティにバインドしたObservableCollectionに操作を行おうとするとエラーとなります。

(上記エラーはUWPプロジェクト、Windows8.1プロジェクトで確認)

xaml(View)

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="asyncawait_test.Page1"
             xmlns:local="clr-namespace:asyncawait_test">
  <ContentPage.BindingContext>
    <local:ViewModel />
  </ContentPage.BindingContext>
    <StackLayout Orientation="Vertical">
      <Button Text="test" Command="{Binding MyCommand}" />
      <ListView ItemsSource="{Binding MyList}" />
    </StackLayout>  
</ContentPage>

ViewModel(一部抜粋)

public const string MyListPropertyName = "MyList";

private ObservableCollection<string> _myList = new ObservableCollection<string>();

/// <summary>
/// ListViewのItemsSource
/// </summary>
public ObservableCollection<string> MyList
{
    get
    {
        return _myList;
    }

    set
    {
        if (_myList == value)
        {
            return;
        }

        _myList = value;
        RaisePropertyChanged(MyListPropertyName);
    }
}

private RelayCommand _myCommand;

/// <summary>
/// ボタンのコマンド
/// </summary>
public RelayCommand MyCommand
{
    get
    {
        return _myCommand
            ?? (_myCommand = new RelayCommand(
                                    async () =>
                                    {
                                        await Task.Factory.StartNew(TestMethod);
                                    }));
    }
}

//awaitで呼び出されるため別スレッドで実行されるメソッド
private void TestMethod()
{
    //エラー
    MyList.Add("aaa");
}

WPFではDispacherを用いて解決しておりましたが、XamarinではDevice.BeginInvokeOnMainThreadメソッドを用いて解決するとよさそうです。

//awaitで呼び出されるため別スレッドで実行されるメソッド
private void TestMethod()
{
    //これならOK
    Device.BeginInvokeOnMainThread(() => MyList.Add("aaa"));
}