非同期データ受信のパターン

一つ前のポストで、仮想COMポートのデータ送受信をSerialPortクラスで扱うライブラリを紹介しました。

DXCommPortManagerの処理の流れを簡単に説明しておきます。Start()メソッドをコールすると、受信処理用のスレッド(スレッドA)が動きます。

スレッドAは、以下の流れでNXTから送られてくるデータを受信しています。

  1. 予め、SerialPortオブジェクトのDataReceivedイベントにコールバックメソッドを登録しておく
  2. ManualResetEventオブジェクトを一つ用意(コードではasyncReceiveEventという名前のオブジェクト)を用意しておく
  3. ManualResetEventオブジェクトをResetし、WaitOne()で待ち
  4. COMポートにデータを受信し、DataReceivedコールバックがコールされたら、ManualResetEventオブジェクトをSet
  5. WaitOne()のブロックが外れるので、SerialPortオブジェクトのRead()を使って受信データを取得し、ReceivedDataリストにデータを登録
  6. 3.に戻る

SerialPortクラスのRead()メソッドをコールすると、受信データが来るまでブロックされるので、そこで待ってもよいのですが、この方法だと受信するデータの長さが可変の場合、予め受信用に用意しておくバッファの大きさをいくつにすればよいかがわかりません。上に挙げた方法であれば、Read()メソッドコールの際、データ長の長さは分かっているので、そこでバッファを適切なサイズ確保できます。他にも、NXTからのデータ送信単位を切り分ける事も可能です。

コールバックを登録して処理のトリガーをかけ、イベントで待っておいて、コールバック内でそのイベントをセットして、処理を行うパターンは、Windows Phone 7のSocketのサンプルでConnectするときに使われているので、覚えておくと便利です。

スレッドAが蓄積した受信データは、GetReceiveData()メソッドで取り込むことができます。このメソッドをコールするスレッドはスレッドAとは別物なので、ReceivedDataリストにlockをかけて排他しています。

スレッドAはずっとNXTからのデータを受信してReceivedDataに蓄積していくわけですが、この流れに以下の処理を追加することにより、応答付きの同期送受信を付け加える事ができます。

  1. 同期送受信である事を示すフラグ(isCurrentSyncing)を用意
  2. 同期送受信用のManualResetEvent(syncReceiveEvent)を用意しておく
  3. SendSyncData()メソッドがコールされたらフラグをtrueに設定し、SerialPortクラスのWrite()メソッドを使ってデータ送信
  4. syncReceiveEventをReset&WaitOneでブロック
  5. SerialPortオブジェクトのDataRecievedコールバック内で、isCurrentSyncingフラグがtrueならsyncReceiveEventをSet
  6. syncReceiveEventのWaitOneブロックが外れるので、SendSyncData()メソッドの引数で渡されたresolverを使って、受信したデータの同期性チェック
  7. 受信データが応答データならばSendSyncData()メソッドの引数として返し、そうでなければ、ReceivedDataリストに登録

こんな流れになっています。これで非同期・同期の処理を混在させつつ、非同期・同期の区別をつけいます。