2016年8月10日水曜日

[C#][UWP]ViewModelを別スレッドから更新する方法

ViewModelをINotifyChangedインターフェイス経由で更新しようとすると
PropertyChangedイベントをRaiseした所でエラーになったりフリーズしたりすることがある。
UIに関する事はすべてUIスレッド内で処理しないとエラーになってしまう事など
実装するときのハマりポイントは難なくクリアしたいので、
処理コストを取っても楽するやり方を見つけたのでメモを残しておく。

方法は、UIスレッドに関する部分をUIHelperクラスに記述しておいて
ViewModel内でプロパティ更新するときにUIHelperに更新を依頼するという流れである。


----
    public static class UIHelper
    {
        static CoreDispatcher uiDispatcher;
        static int uiThreadId;
        public static void Initialize(CoreDispatcher uiDispatcher, int uiThreadId)
        {
            UIHelper.uiDispatcher = uiDispatcher;
            UIHelper.uiThreadId = uiThreadId;
        }

        public static bool IsUIThread => uiThreadId == Environment.CurrentManagedThreadId;

        public static async void Dispatch(Action action)
        {
            if (IsUIThread)
            {
                action();
            }
            else
            {
                await uiDispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(action));
            }
        }
    }
----

上記クラスを初期化するInitializeメソッドは
Appクラスの頭あたりで実行しておくのが良い。

UIHelper.Initialize(Dispatcher, Environment.CurrentManagedThreadId);



ViewModelの実装サンプル

----
    public class viewmodel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer.Default.Equals(field, value)) return false;
            field = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            return true;
        }

        int _value1;
        public int value1
        {
            get { return _value1; }
            set { UIHelper.Dispatch(() => { SetField(ref _value1, value); }); }
        }
    }
----

SetFieldの処理の上にActionでくるんでDispatchに放り投げてしまう。
値1つ変更するのになんでこんなコストを払わないといけないんだろうって思ってしまうけど
安定してシンプルになるならコストを払おう。

0 件のコメント:

Androider