概要
シリアル通信中、相手がいつ送信してくるかわからない場合、受信処理は一定時間で終了してほしい。
しかし、UWPのシリアル通信に用意されているメソッドをそのまま使用するとそれがかなわない。
この問題の回避策を述べる。
発生した環境
この問題はRaspberry Pi 3 + Windows IoTの環境でおきた。
環境が異なると状況も違うかもしれない。
問題
SerialDevice
クラスを使用してデータを受信する場合、このオブジェクトのInputStream
プロパティを使用して、Windows.Storage.Streams.DataReader
オブジェクトを生成する。
その後、DataReader
オブジェクトのLoadAsync
メソッドを呼び出すことになるのだが、このメソッドが返すTask
オブジェクトは、実際にデータの受信がおこなわれるまで終了しない。
たとえ、
-
SerialDevice
オブジェクトのReadTimeout
プロパティをいくつに -
DataReader
オブジェクトのInputStreamOptions
の値をPartial
以外に
したとしても。
以下がタイムアウトが働かないコード。
SerialDeviceObject.ReadTimeout = TimeSpan.FromMilliseconds(1000);
DataRederObject.InputStreamOptions = InputStreamOptions.None;
uint bytesRead = await DataReaderObject.LoadAsync(256);
このLoadAsync
はデータを1バイト以上受信するまで、いつまでたっても終わらない。
解決策
そこで、CancellationTokenSource
を使用して、LoadAsync
のタスクを中断するようにする。
SerialDeviceObject.ReadTimeout = TimeSpan.FromMilliseconds(1000);
uint bytesRead = 0;
using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(1500))
{
try
{
bytesRead = await DataReaderObject.LoadAsync(256).AsTask(cts.Token);
}
catch (OperationCanceledException)
{
// timeout
}
}
これで受信できなかった場合はタイムアウトするようになる。
CancellationTokenSource
に与える時間が、SerialDevice.ReadTimeout
よりも大きいことに注意してほしい。
DataReader.LoadAsync(uint)
は、
- どれだけデータを受信しようとも、最低限、
SerialDevice.ReadTimeout
の時間だけはデータを待ち続ける - 1byte以上データを受信するまで終わらない
という動作をする。ReadTimeout
はタイムアウトというよりも受信固定時間なのだ。
そのため、CancellationTokenSource
を使用してLoadAsync
のタスクを終了させる場合、データを受信しつつあるLoadAsync
を中断してしまわないよう、SerialDevice.ReadTimeout
よりも長い時間で中断するようにしたほうがよい。
考察
ここで説明した方法では例外を使用することになる。
ご存知のとおり、例外というものは時間的コストがかかる、非常に重い処理のうちの一つである。
- Raspberry Pi 3 + Windows IoT
-
ReadTimeout
に10ms、CancellationTokenSource
に設定する例外発生時間を60ms
として実行したところ、何も受信しなかった場合、受信処理に200msから350msを要した。
(何か受信できる場合は、10msで処理が終了する)
単純に考えて、受信開始から例外発生までの60msを除いた140msから290msが例外を処理している時間になる。
常に相手側からの送信を待ちうけてこちらからはその返信しかしない場合を除けば、受信から送信まで100msや200ms程度のレスポンスを保つことは不可能ということになる。
他にタイムアウトを適用した受信を可能とする方法があるのであれば話は変わるのだが。
参考にした情報
SerialDevice.ReadTimeout not working
https://social.msdn.microsoft.com/Forums/en-US/7b9fa7b5-55a8-490d-ab71-ea3bcf047826/serialdevicereadtimeout-not-working?forum=WindowsIoT
Unable to use SerialDevice.ReadTimeout in Windows 10 IoT
https://stackoverflow.com/questions/32157918/unable-to-use-serialdevice-readtimeout-in-windows-10-iot