2016年7月27日水曜日

UWP:アプリケーションは、別のスレッドにマーシャリングされたインターフェイスを呼び出しました。 (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))

ViewModelにINotifyPropertyChangedインターフェイスを使っていて、用意したTask処理の中でViewModeを更新するとこういったエラーが起きる。

原因は、UIスレッド以外でUIを更新しようとしたからである。
これを対処するにはタスク処理からUIスレッドに処理を実行してもらうことが必要になる。
(データクラスはGet;set;で1行で終わらせられるのを展開して実装しないといけないし、UIスレッドの面倒まで見ないといけないなんてめんどくさいこの上ない。)

この原因を紐解くにはまずTaskの仕組みについて知る必要がある。

~(Task Async Awaitとは)~
 スレッドA(UIスレッド)、スレッドB(別スレッド)の2つあったとしよう。
スレッドAでTaskクラスを実行すると、処理はスレッドBで実行される。
逆にスレッドBでTaskクラスを実行すると、スレッドAになる。
これがTaskクラスで、重たい処理は他スレッドにやらせることが基本動作になる。
Taskクラスの処理が終わりを知るのにawaitを使う。

まとめると、
Task ・・・ 処理の単位
Async ・・・ 別スレッドで動作する処理がある
Await ・・・ 別スレッドからの帰りを待つ

・Taskを作っただけでは別スレッドにはならない。
・タスクをRunさせることでスケジューラに登録されて処理が開始される。


ざっくり書いたがこんな感じに覚えておけば良い。


例でボタンクリックイベントメソッドを使った時の挙動を見てみよう。

OK:そもそも別スレッドでViewModelを更新してないので問題なし

        private void button_Click(object sender, RoutedEventArgs e)
        {
            vm.Update();
        }

NG:ボタンクリックメソッド内のタスク実行したので、別スレッドでUI更新している。

        private async void button_Click(object sender, RoutedEventArgs e)
        {
            await Task.Run(async () =>
            {
                await Task.Delay(1000);
              vm.Update();
            });
        }


OK:ボタンクリックメソッド内でUI更新したので問題なし。
        private async void button_Click(object sender, RoutedEventArgs e)
        {
            await Task.Run(async () =>
            {
                await Task.Delay(1000);
            });
            vm.Update();
        }

OK:2つ上のと違ってUIスレッドで動いているボタンクリックメソッド上でawaitしているので問題なし。

        private async void button_Click(object sender, RoutedEventArgs e)
        {
            await Update();
        }

        async Task Update()
        {
            await Task.Delay(1000);
            vm.Update();
        }


-----

つまり、

・View上のボタンクリックメソッドはタスク処理でないので、UIスレッドで動作しているというのが分かる。
・asyncメソッド内部で複数のTaskが存在していても、それぞれがスケジューラに登録されて実行刷るわけではないので、すべて同スレッドで実行される。
・タスクをRunしたらその時点で別スレッド開始する。

なんとなく分かっただろうか。
少しだけスケジューラについて触れておくと、デバッグ時にタスクウィンドウで現在どのようなタスクが動いているかが見れるようになっていて便利。


最後に別スレッドからUIを更新するには、ViewクラスにCoreDispatcherクラスで定義されたDispatcherプロパティがあるので、これをタスク内にあるUI更新部分を括ると回避する方法を下記にのせておく。


asyncメソッド内
・・・
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
    Update();
);
・・・




0 件のコメント:

Androider