2017年1月3日火曜日

Xmarin+Android framework で RunOnUiThread() を使うとデータが化けた話

 Xmarin+Android framework でシステム開発中。

 ストリーミングのデータを処理し、エラーを見つけたらリアルタイムに画面に表示する機能を実装していた。

 表示にはエラーが発生した時刻(ms まで)と期待したデータと実際に受信したデータを表示するようにしたのだが、期待したデータの表示が、こちらが期待した値にならない。データ処理ロジックが間違っているのかと数時間ソースを追ったのだが原因がつかめない。

(ちなみに、どういうわけかブレークポイントが使えない。設定するとプログラムが止まるタイミングでアプリが終了する)

 いろいろやっていくうちに、「画面表示だと、別スレッドだから時間表示がずれる。ログに吐かせてみよう」ということで、同じ内容をログにはかせると期待したデータが表示された!

----
 原因だが、RunOnUiThread() に渡したコードに、クラス内で定義した変数を持たせたために、表示スレッドが実行される前にその変数が更新されてしまうことが原因だった。

class foo
  {
  int LastData;

  void bar (int data)
  {
    ....
    parentActivity.RunOnUiThread(() =>
    {
      parentActivity.UpdateTestInfo("Error Info: " + DateTime.Now.ToString("HH:mm:ss.fff") + ":" + LastData.ToString("x2") + "-" + data.ToString("x2") + "\n");
    }
  }

 というようなコードを書いたのだが、コードが UiThread のキューに積まれるときに、

  • date は関数終了と同時に消滅するローカル変数だから値渡し
  • LastData は、関数が終了しても消滅しないので参照渡し
となったらしい。

 ストリーミングデータを処理しているため LastData は常に更新されていく。そのため画面に表示されるのは、RunOnUiThread() が実行された時点での LastData の値ではなく parentActivity.UpdateTestInfo(...)が実行されるときの LastData の値、
 date の方は値渡しなのでRunOnUiThread() が実行された時の値に。

 変数は参照わたしなんかしないで、キューに積む時点の値で全部値渡しにしてしまえばいいのに。