質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
86.12%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

解決済

高速シリアル受信の実装方法について

pitagora
pitagora

総合スコア14

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

4回答

0グッド

1クリップ

814閲覧

投稿2022/10/13 00:59

前提

Windows10のUSB仮想シリアルドライバを利用してUSBフルスピード機器から測定データ受信をさせようとしています。
バルク転送で理論ボーは約2.1Mbps相当となります。
USB機器は停止要求か通信エラーが発生するまで連続で絶えずデータを送ってきます。

実装としては、

  • C#のSerialPortクラスDatarecievedイベントを利用して、Serialポートバッファにデータが溜まったら通知を受けてアプリのバッファに格納する処理を行っています。
  • system.Timers.Timerで100ms間隔でアプリバッファの内容を解釈させております。

現状受信イベントが遅れることがあり、Serialバッファが溢れる問題に直面しております。

実現したいこと

目標として3h程度は安定して受信したい。(現状0分~30分で問題発生)
-> 他の処理があっても、Serial受信->バッファへ格納を最優先に処理させたいです。

発生している問題・エラーメッセージ

受信イベント発生が遅れSerrialバッファが溢れて、取りこぼしが発生します。
バッファ満タンの状態になると、USB機器が送信を停止し、一定時間以内に解消されなければエラーを出して送信を止めてしまいます。
serialPort.ReadBufferSizeを大きくしても内部的にはサイズが変わらないようです。(usbser.sysの制約?)

現状は以下がわかっています。
① 受信イベント発生が遅れ、急に大きなデータがSerialバッファ溜まる。大抵この時点でバッファが溢れる。
②windowsの他の操作をすると処理が遅れる・・・気がする。(word等のソフト立ち上がる、フォーカスを変える)
③100msタイマーを切ると、イベントの遅れが少なくなり、溢れるまでの時間が長くなる・・・気がする。

問題発生時のログ
No.26449:受信イベント6,サイズ2294,前回からの経過時間7.247ms
スレッド6:バッファ格納時間0.041ms,格納サイズ2294
No.26450:受信イベント6,サイズ2046,前回からの経過時間6.5969ms
スレッド6:バッファ格納時間0.1644ms,格納サイズ2046
No.26451:受信イベント17,サイズ1984,前回からの経過時間6.2961ms
スレッド17:バッファ格納時間0.3198ms,格納サイズ1984
No.26452:受信イベント21,サイズ1612,前回からの経過時間5.16ms
スレッド21:バッファ格納時間0.0484ms,格納サイズ1612
No.26453:受信イベント6,サイズ12338,前回からの経過時間156.9381ms 

※↑ここで問題が発生。通信が終了する。通常の10倍以上時間がかり、理論ボーからするとバッファが溢れているようです。

該当のソースコード

現状GUIは受信開始、停止ボタンのみのため、受信とタイマー関係のみ抜粋しています。
(定義が見えないものはstaticで宣言しております。)

C#

1//form.cs 2 3public form() 4{ 5 // タイマ準備(100ms) 6 timer1 = new System.Timers.Timer(time_interval); 7 this.timer1.Elapsed += (sender, e) => Timer1_Tick(sender, e) 8} 9 10private void Timer1_Tick(object sender, EventArgs e) 11 { 12 timer1.Stop(); 13 time_cnt += 1; 14 if (timerThread != null && !timerThread.IsCompleted) 15 return; 16 timerThread = Task.Run(() => 17 { 18 // 解析 19 data_analysis(); 20 } 21 timer1.Start(); 22 } 23 24/* 動作開始*/ 25private void start_button_Click(object sender, EventArgs e) 26{ 27 28 // USB機器へ送信要求 29 usb_start(); 30 timer1.Start(); 31} 32

C#

1// com.cs 2 3private void serialPort_DataReceivedobject sender, SerialDataReceivedEventArgs e) 4 { 5 // イベント停止(あってもなくても結果は変わらない模様) 6 serialPort.DataReceived -= new SerialDataReceivedEventHandler(serialPort_DataReceived); 7     lock (lockObject) 8 { 9 int id = 0; 10 int worker = 0; 11 int io = 0; 12 13 // ポートチェック 14 if (serialPort != null && serialPort.IsOpen) 15 { 16 SerialPort sp = (SerialPort)sender; 17 id = System.Threading.Thread.CurrentThread.ManagedThreadId; 18 19 try 20 { 21 // バッファサイズ確認 22 int rs = sp.BytesToRead; 23 TimeSpan span = sw.Elapsed; 24 sw.Stop(); 25 Console.WriteLine($"No.{cnt}:受信イベント{id},サイズ{rs},前回からの経過時間{span.TotalMilliseconds}ms"); 26 sw.Restart(); 27 28        // データがあれば処理 29 if (0 != rs) 30 { 31             // 解析を考慮してパケットサイズで格納(あってもなくても結果は変わらない模様) 32 rs = (rs / Constants.PACKET_SIZE) * Constants.PACKET_SIZE; // 33 34 // バッファ格納(関数で飛ばさず、ここでSerialReadして読み捨てても結果は変わらない模様。) 35 set_buffer(ref rs); 36 37 //Console.WriteLine($"No.{cnt}:スレッド{id},終了:経過時間{span.TotalMilliseconds}ms"); 38 cnt++; 39 40 } 41 else 42 { 43 Console.WriteLine("受信なしで抜けている"); 44 } 45 } 46 catch { 47 Console.WriteLine("異常発生") 48            } 49 } 50 } 51     // イベント許可(あってもなくても結果は変わらない模様) 52 serialPort.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived); 53 } 54 55

C#

1// rec.cs 2public void set_buffer(ref Int32 size) 3{ 4 Int32 id; 5 id = System.Threading.Thread.CurrentThread.ManagedThreadId; 6 sw.Restart(); 7 // バッファ格納実体 8 sp.Read(log_buffer, wp, size); 9 wp += (Int32)size; 10 if (wp >= max_index) 11 { 12 wp = 0; 13 } 14 TimeSpan span = sw.Elapsed; 15 Console.WriteLine($"スレッド{id}:バッファ格納時間{span.TotalMilliseconds}ms,格納サイズ{size}"); 16}

試したこと

最小コードとして以下のみを実行しました。
発生までの時間は伸びた気がしますが、限定的な改善でした。

C#

1//form.cs 2 3private void start_button_Click(object sender, EventArgs e) 4{ 5 6 // USB機器へ送信要求 7 usb_start(); 8 // タイマ動作なし 9} 10

C#

1private void serialPort_DataReceivedobject sender, SerialDataReceivedEventArgs e) 2 { 3 int id = 0; 4 int worker = 0; 5 int io = 0; 6 7   SerialPort sp = (SerialPort)sender; 8 id = System.Threading.Thread.CurrentThread.ManagedThreadId; 9 10 // バッファサイズ確認 11 Int rs = sp.BytesToRead; 12 TimeSpan span = sw.Elapsed; 13 sw.Stop(); 14 Console.WriteLine($"No.{cnt}:受信イベント{id},サイズ{rs},前回からの経過時間{span.TotalMilliseconds}ms"); 15 sw.Restart(); 16 17    // データがあれば処理 18 if (0 != rs) 19 { 20  21 // 読み捨て 22 sp.read(buf,0,rs); 23 24 Console.WriteLine($"No.{cnt}:スレッド{id},終了:経過時間{span.TotalMilliseconds}ms"); 25 cnt++; 26 27 } 28 else{ } 29 30 return; 31 } 32 33

補足情報(FW/ツールのバージョンなど)

VisualStudioExpress 2017
C#
ここにより詳細な情報を記載してください。

以下のような質問にはグッドを送りましょう

  • 質問内容が明確
  • 自分も答えを知りたい
  • 質問者以外のユーザにも役立つ

グッドが多くついた質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

気になる質問をクリップする

クリップした質問は、後からいつでもマイページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

下記のような質問は推奨されていません。

  • 間違っている
  • 質問になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

適切な質問に修正を依頼しましょう。

ozwk

2022/10/13 01:53

GUIなしでコンソールで作るとどうなります?
Zuishin

2022/10/13 02:19

読み出しルーチンをタイマー起動してロックしてるからじゃないんですか?
KOZ6.0

2022/10/13 02:24

ハンドシェイクを有効にできないでしょうか?(Handshake プロパティを RequestToSend にする) パフォーマンスは落ちてしまいますが、取りこぼしが起きるより良いと思います。
dodox86

2022/10/13 02:42

最近は少ないかもしれませんが、USB/シリアル変換のチップ自体やそのサードパーティ製のデバイスドライバーの性能、出来が悪い場合があります。FTDI社製など有名どころのものでは問題無いと思いますが。 デバイスマネージャーで、該当デバイスの低レベルの設定ができるかもしれないので、覗いてみてください。
dodox86

2022/10/13 02:47

> デバイスマネージャーで、該当デバイスの低レベルの設定ができるかもしれないので、覗いてみてください。 得体の知れないサードパーティ製USBシリアル変換アダプターではなく、デバイスマネージャーで確認してデバイスが正常に稼働しているのであれば先にコメントをいただいている皆さんのアドバイスのようにまず、ソフト(アプリ)側の問題解決を優先すべきではあります。
KOZ6.0

2022/10/13 03:44

ググってたら面白い記事をみつけました。 【ググっても無駄!】WindowsのRS-232Cシリアル通信で受信データに欠落が発生する https://gabekore.org/windows-rs232c-deficit-recv-data アレコレやってダメなら、ここに書かれているようにハードウエアを交換するしかないでしょうね。
pitagora

2022/10/13 09:57

説明が悪くて申し訳ございません。 シリアル変換器は介在しません。ネイティブUSB機器とPCUSBポートの接続です。 ドライバが仮想シリアルで、アプリ層からはシリアルとしてアクセスしています。 (PCまで完全にUSB接続。FTDIのようなICはいません。)
dodox86

2022/10/13 10:29

USBデバイスのネイティブのバルク転送を使っているということですか。そうであると事情はちょっと特殊で、シリアル通信として一般的なRS-232Cでの通信の話にはならないので、ソフトだけの問題ではなさそうです。ハード側(USBデバイス側)の仕様を意識しつつ、問題に触れないよう、注意深くソフトを作るしかなさそうです。
KOZ6.0

2022/10/13 10:59

matukeso さんの回答にあるように、受信専用スレッドを作るのがよさそうに思います。 SerialPort.BaseStream で Stream オブジェクトが取れるので、非同期メソッドを使って常に受信しつつバッファに格納するといいんじゃないでしょうか。
pitagora

2022/10/13 22:02

ありがとうございます。 専用スレッド、BaseStreamあたりを調べてみます。 単純なEventで受信すると他の処理が重なった場合に遅れるのでしょうか。 組込みのベクタテーブルの優先順位同等の設定ができれば良いのかな?というイメージです。
KOZ6.0

2022/10/14 01:58

>単純なEventで受信すると他の処理が重なった場合に遅れるのでしょうか。 通知をうけてから受信処理するのと、常に受信処理しているのとを比べると、後者のほうがおそらく速いのかなと思いますが、やってみないとわからないところではあります。 サンプルを書いてみたので確認してみてください。 読み取りバッファを2つ用意して、片方読み取りしている間に読み取り済みのバッファを処理するのがキモです。 SerialPort の設定や PACKET_SIZE が不明でしたので実際の値に修正してください。

回答4

0

USB 機器がフロー制御に対応していないと十分なスピードマージンがない限り取りこぼしを完全に防ぐことはできないでしょう。
https://blog.oino.li/posts/usbcdc/
またはアプリケーションレベルでパケットの再送やエラー訂正をおこなうことになりますが、こちらも機器側での対応が必要です。

投稿2022/10/18 00:41

n24bass

総合スコア19

良いと思った回答にはグッドを送りましょう。
グッドが多くついた回答ほどページの上位に表示されるので、他の人が素晴らしい回答を見つけやすくなります。

下記のような回答は推奨されていません。

  • 間違っている回答
  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

0

ベストアンサー

処理を書いてみたんですがテスト環境がないのでテストできてません。
読み込みバッファを2つ用意して切り替えながら受信します。

C#

1using System; 2using System.IO; 3using System.IO.Ports; 4using System.Threading; 5 6class Program 7{ 8 static void Main(string[] args) { 9 SerialPort sp = new SerialPort(); 10 // SerialPort の設定を書く 11 sp.PortName = "COM1"; 12 sp.Open(); 13 14 var th1 = new Thread(new ParameterizedThreadStart(ReadTask)); 15 th1.Priority = ThreadPriority.AboveNormal; // 優先度を少し上げる 16 th1.Start(sp); 17 18 var th2 = new Thread(new ParameterizedThreadStart(AnalizeTask)); 19 var signal = new ManualResetEvent(false); 20 th2.Start(signal); 21 22 Thread.Sleep(1000); 23 Console.WriteLine("何かキーを押すと終了します。"); 24 Console.ReadKey(); 25 26 sp.Close(); 27 th1.Join(); 28 signal.Set(); 29 th2.Join(); 30 } 31 32 class Constants 33 { 34 public const int PACKET_SIZE = 64; // パケット長? 35 public const int BLOCK_SIZE = 10; // 一度に読むパケット数 36 public const int READ_SIZE = PACKET_SIZE * BLOCK_SIZE; // 一度に読むバイト数 37 38 public const int LOG_SIZE = 1024; // リングバッファに格納するパケットの数 39 public const int ANALIZE_SLEEP = 100; // 解析処理の Sleep 間隔 40 // SerialPort を Close したときに発生する IOException の HResult 41 public const int APPLICATION_CANCEL = unchecked((int)0x800703e3); 42 } 43 44 static void ReadMain(SerialPort serialPort) { 45 var stream = serialPort.BaseStream; 46 var current = SwitchBuffer(null); 47 // 1回目は同期 48 var readedLength = stream.Read(current, 0, Constants.READ_SIZE); 49 while (true) { 50 // 2回目以降は非同期 51 var buffer = SwitchBuffer(current); 52 var task = stream.ReadAsync(buffer, 0, Constants.READ_SIZE); 53 if (readedLength > 0) { 54 // バッファに格納 55 logBuffer.SetData(current, readedLength); 56 } 57 task.Wait(); 58 readedLength = task.Result; 59 current = buffer; 60 } 61 } 62 63 static byte[] buffer1 = new byte[Constants.READ_SIZE]; 64 static byte[] buffer2 = new byte[Constants.READ_SIZE]; 65 66 static byte[] SwitchBuffer(byte[] current) { 67 if (ReferenceEquals(buffer1, current)) { 68 return buffer2; 69 } else { 70 return buffer1; 71 } 72 } 73 74 static void ReadTask(object obj) { 75 Console.WriteLine("受信処理を開始しました。"); 76 SerialPort sp = (SerialPort)obj; 77 try { 78 ReadMain(sp); 79 } catch (AggregateException aex) { 80 foreach (var ex in aex.InnerExceptions) { 81 var ioex = ex as IOException; 82 if (ioex != null && ioex.HResult == Constants.APPLICATION_CANCEL) { 83 return; 84 } else { 85 Console.WriteLine(ex.Message); 86 } 87 } 88 throw; 89 } catch (IOException ioex) { 90 if (ioex.HResult == Constants.APPLICATION_CANCEL) { 91 return; 92 } 93 Console.WriteLine(ioex.Message); 94 throw; 95 } finally { 96 Console.WriteLine("受信処理を終了しました。"); 97 } 98 } 99 100 101 static void AnalizeTask(object obj) { 102 Console.WriteLine("解析処理を開始しました。"); 103 WaitHandle waitHandle = (WaitHandle)obj; 104 while (!waitHandle.WaitOne(Constants.ANALIZE_SLEEP)) { 105 byte[] packet; 106 while (logBuffer.GetData(Constants.PACKET_SIZE, out packet)) { 107 // packet を処理 108 } 109 } 110 Console.WriteLine("解析処理を終了しました。"); 111 } 112 113 static RingBuffer logBuffer = new RingBuffer(Constants.PACKET_SIZE * Constants.LOG_SIZE); 114 115 class RingBuffer 116 { 117 byte[] dataStore; 118 int capacity; 119 int top = 0; 120 int bottom = 0; 121 122 public RingBuffer(int capacity) { 123 this.capacity = capacity; 124 dataStore = new byte[capacity]; 125 } 126 127 public int StoreLength { 128 get { 129 return bottom < top ? bottom + capacity - top : bottom - top; 130 } 131 } 132 133 public void SetData(byte[] data, int length) { 134 lock (this) { 135 if (length + StoreLength > capacity) { 136 throw new InternalBufferOverflowException(); 137 } 138 if (bottom + length > capacity) { 139 int copyLength = capacity - bottom; 140 if (copyLength > 0) { 141 Array.Copy(data, 0, dataStore, bottom, copyLength); 142 } 143 bottom = length - copyLength; 144 Array.Copy(data, copyLength, dataStore, 0, bottom); 145 } else { 146 Array.Copy(data, 0, dataStore, bottom, length); 147 bottom += length; 148 } 149 } 150 } 151 152 public bool GetData(int length, out byte[] data) { 153 lock (this) { 154 if (length > StoreLength) { 155 data = null; 156 return false; 157 } 158 data = new byte[length]; 159 if (top + length > capacity) { 160 int copyLength = capacity - top; 161 if (copyLength > 0) { 162 Array.Copy(dataStore, top, data, 0, copyLength); 163 } 164 top = length - copyLength; 165 Array.Copy(dataStore, 0, data, copyLength, top); 166 } else { 167 Array.Copy(dataStore, top, data, 0, length); 168 top += length; 169 } 170 return true; 171 } 172 } 173 } 174}

投稿2022/10/13 16:30

KOZ6.0

総合スコア2252

良いと思った回答にはグッドを送りましょう。
グッドが多くついた回答ほどページの上位に表示されるので、他の人が素晴らしい回答を見つけやすくなります。

下記のような回答は推奨されていません。

  • 間違っている回答
  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

pitagora

2022/10/14 07:32

ありがとうございます。 元々リングバッファを構成していたので既存コードには簡単に繋がりました。 しかし、すぐにバッファオーバーとなり送信側が止めています。 Constants.READ_SIZE=1984なのですが、 var task = stream.ReadAsync(buffer, 0, Constants.READ_SIZE); この結果は1984となるのでしょうか?(現状1,124,1984,372とまばらです。)
KOZ6.0

2022/10/14 08:03

>元々リングバッファを構成していたので既存コードには簡単に繋がりました。 ってことは、このコードにコマンド送信の部分をつけて動かしたのでなく、元のプログラムに手を加えたわけですね? 手を加えた状態のコードがわからないので何とも言えないです。 戻り値については、まばらになるかもしれません。
KOZ6.0

2022/10/14 08:19

ところで、1984 という数字の根拠は? 大きくしても効果ないでしょうか?
KOZ6.0

2022/10/14 08:30

このコードにコマンド送信の部分をつけて動かすことはできないですか? 元のコードは見えないところが多いので他に原因があったとしても把握できません。
pitagora

2022/10/15 02:31 編集

>>ところで、1984 という数字の根拠は? >>大きくしても効果ないでしょうか? 1フレーム62バイト(エンドポイント64バイトに対して余裕がある模様)の定数倍です。 最小の62バイトでは即止まったので、大きくしていって今に至ります。 解析スレッドは開始せず、受信スレッドのみ回し、アプリバッファへの転送もしない以下のコードを走らせてみました。その他タイマー等一切動かしていません。 ``` C# private void stream_read(SerialPort serialPort) { TimeSpan span; var stream = serialPort.BaseStream; var current = SwitchBuffer(null); var readLength = stream.Read(current, 0, Constants.REC_PACKET_SIZE); Console.WriteLine($"1回目は同期受信{readLength}"); rec_buffer_size += readLength;// 総受信データ数(クラス変数) while(true) { #if DEBUG sw.Restart(); Console.WriteLine($"スレッド{System.Threading.Thread.CurrentThread.ManagedThreadId}:非同期受信開始"); #endif var burst_buf = SwitchBuffer(current); var task = stream.ReadAsync(burst_buf, 0, Constants.REC_PACKET_SIZE); if(readLength > 0) { #if DEBUG Console.WriteLine($"ポートバッファ残量{serialPort.BytesToRead}"); #endif } task.Wait(); readLength = task.Result;//非同期受信結果 #if DEBUG sw.Stop(); span = sw.Elapsed; Console.WriteLine($"スレッド{System.Threading.Thread.CurrentThread.ManagedThreadId}:受信終了経過時間{span.TotalMilliseconds},受信サイズ{readLength}"); #endif rec_buffer_size += readLength; current = burst_buf; } } ``` ```C# // 受信開始 private void start_button_Click(object sender, EventArgs e) { Thread rec_th = new Thread( new ThreadStart(test)); rec_th.Priority = ThreadPriority.Highest; var th_analize = new Thread(new ParameterizedThreadStart(Analize_task)); var signal = new ManualResetEvent(false); var th_read = new Thread(new ParameterizedThreadStart(Read_task)); th_read.Priority = ThreadPriority.Highest; th_read.Start(sp_inst); Console.WriteLine("データ要求コマンド->0x01送信"); Ctrl.Set_logger_start(); //th_analize.Start(signal); } ``` * Consoleログ データ要求コマンド->0x01送信 1回目は同期受信1 スレッド12:非同期受信開始 ポートバッファ残量62 スレッド12:受信終了経過時間0.4659,受信サイズ185 スレッド12:非同期受信開始 ポートバッファ残量0 スレッド12:受信終了経過時間0.624,受信サイズ186 スレッド12:非同期受信開始 ポートバッファ残量62 スレッド12:受信終了経過時間0.1062,受信サイズ124 スレッド12:非同期受信開始 ポートバッファ残量0 スレッド12:受信終了経過時間0.0855,受信サイズ62 スレッド12:非同期受信開始 ポートバッファ残量0 スレッド12:受信終了経過時間0.085,受信サイズ62 スレッド12:非同期受信開始 ポートバッファ残量0 スレッド12:受信終了経過時間0.3001,受信サイズ1 スレッド12:非同期受信開始 ポートバッファ残量0 ~ 何回か経過 ~ ポートバッファ残量61 スレッド12:受信終了経過時間0.1272,受信サイズ1 スレッド12:非同期受信開始 ポートバッファ残量0 スレッド12:受信終了経過時間79.381,受信サイズ61 スレッド12:非同期受信開始 ポートバッファ残量10354 スレッド12:受信終了経過時間0.0984,受信サイズ1984 スレッド12:非同期受信開始 ポートバッファ残量8370 ~ 数回受信し、データが来なくなったので停止 ~ ログ停止 total_read_size = 23372Byte * Wiresharkログ 95 0.000668 1.13.1 host USB 89 URB_BULK in (受信開始) 97 0.000105 1.13.1 host USB 89 URB_BULK in 99 0.000143 1.13.1 host USB 89 URB_BULK in ~ 総計377回受信 ~ PC自身は遅れても最大0.250msで受信している模様です。 ペイロード62Byteなので総受信データは23,374Byteとなり、アプリで受信した数と合致します。 つまり、USB->アプリ間の取りこぼしはなし。 アプリ側で非同期受信完了に79msかかっている箇所でUSBポートがパンク、デバイス側が送信停止するという流れになります。
pitagora

2022/10/15 04:05 編集

イベントで行っていた時と同じく次の受信が安定に実施できていないような感じです。 ※コメントでマークダウンを使う方法がわからなくてコード部が見にくくなっています。申し訳ございません。 追記: 上手くパターン??にはまったら以下の繰り返しで30分ぐらい動きました。(何故かメモリ消費が肥大してましたが。) スレッド15:受信終了経過時間0.0483,受信サイズ61 スレッド15:非同期受信開始 ポートバッファ残量0 スレッド15:受信終了経過時間0.1693,受信サイズ1 スレッド15:非同期受信開始 ポートバッファ残量0 スレッド15:受信終了経過時間0.1008,受信サイズ61 スレッド15:非同期受信開始 ポートバッファ残量0 >>このコードにコマンド送信の部分をつけて動かすことはできないですか? その実装になっているつもりですが、新たなプロジェクト起こして試してみます。 (現状、元々宣言していたタイマー等はそのまま存在してます。startしていないだけだと影響ありますかね)
KOZ6.0

2022/10/15 04:14

コメントでは MarkDown は使えません。質問を編集して追記すれば良いと思います。 現状、受信が追いつかなくて困っているのですから、ログ出力は最小限にしてください。 3点ほど気になったので挙げておきます。 (1) Console にログを出力 Console出力は遅いので、ファイルに出力するようにしたほうが良いです。 var writer = new StreamWriter(@"c:\test\logger.log", true); writer.WriteLine("test"); writer.Flush(); writer.BaseStream.SetLength(st.BaseStream.Position); こんな感じで、WriteLine ~ BaseStream.SetLength までをひとつのメソッドとしておけば、StreamWriter を閉じなくても書いたものが失われることはありません。 (2) ログを出力する位置。 ReadAsync ~ Task.Wait の間なら影響は少ないですが、Task.Wait ~ 次の ReadAsync に入れるのは良くないです。 せっかくバッファ切り替えして、常に読み取り要求を実行している状態を保とうとしているのに意味がなくなってしまいます。 (3) serialPort.BytesToRead このメソッドは、ClearCommError API を呼び出して、通信デバイスの現在の状態を確認するため、パフォーマンスに影響を及ぼす可能性があります。 極力避けてください。
KOZ6.0

2022/10/15 06:24

SerialPort クラスではいろいろ処理が行われていますので、どうしても処理が追いつかない場合は、API を直に呼び出のもひとつの手かと思います。 その場合、SerialPort の実装部分は SerialStream クラスに集約されていますので、参考にすると良いでしょう。 「SerialStream.cs」 https://referencesource.microsoft.com/#System/sys/system/io/ports/SerialStream.cs,b120632fda7c01ba 他には、LibUsbDotNet なんてのもあるようです。 「LibUsbDotNet」 https://www.nuget.org/packages/LibUsbDotNet/3.0.97-alpha LibUsbDotNetを使ってUSB機器と通信するアプリケーションを作成する (C#プログラミング) https://www.ipentec.com/document/libusbdotnet-app-create
pitagora

2022/10/16 04:29

ご返信ありがとうございます。 >>(1) Console にログを出力 >>(2) ログを出力する位置 なるほど・・・表示でリソースを使うのも問題なんですね。 試しにConsole出力を全部切っても状況か変わらずでしたので、 今回の件では影響度は小さかったようですが注意したいと思います。 >>SerialPort クラスではいろいろ処理が行われていますので 提案いただいたAPIを直接操作するのも試してみようと思います。 (単純にwin32apiのラッパーになっているのならそんなに大変ではなさそう?) ふと、試しにスレッド立てた後に一定時間ごとに同期受信で処理してもダメなのか?と思い試してみました。 結果としては3h程度問題なく動作するケースも出てきましたが、相変わらずコケる場合もあります。 (ご指摘頂きましたがそのままBytesToRead使っています。) 状況を整理したいのですが知識が足りず、何で成立するのか、デメリットは何かというのがわかっていません。 ご助言いただけましたら幸いです。 ↓試したコード // 発火 private void start_button_Click(object sender, EventArgs e) { Thread serial_th = new Thread( new ThreadStart(thead_serial_read)); serial_th.Priority = ThreadPriority.AboveNormal; serial_th.start(); } // スレッド内処理 void thread_serial_read() { Int32 size = 0; while(true) { radLength = serialPort.BytesToRead(); if(readLength >= Constants.PACKET_SIZE)// 62Byte以上来たら { serialPort.Read(buffer,wp,readedLength);// 直接リングバッファに格納 } Thread.Sleep(15); //理論上バッファ溢れない&GUIに返したい      if(stop_button) { break; } } }
KOZ6.0

2022/10/16 07:51

試したパターンを整理し、ログ出力や StopWatch など余計な操作を外して同じ条件でテストしてみてはいかがでしょうか? 優先度を Hiest から AboveNormal にしてみたり、WireShark をつかって監視もしてるようなので、本当に同じ条件でテストした結果なのかわからないです。
pitagora

2022/10/16 21:11

色々ありがとうございました。再度勉強していきます。

0

DataRecvedイベントに期待せず、専用スレッドとかで読み続けてみては?
Threadを使ってもいいし、

csharp

1void ReadThreadProc(){ 2 while( SerialPort.IsOpen) { 3 byte[] arr = new byte[1024]; 4 int readlen = SerialPort.BaseStream.Read( arr, 0, arr.Length ); 5 OnRead( arr, readlen); 6 } 7}

ReadAsyncとか使ってもいいし。

csharp

1Task ReadAsync( byte[] buffer){ 2 return SerialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length).ContinueWith((Task<int> len) => OnRead(buffer, len.Result)); 3} 4void OnRead(byte[] buffer, int len){ 5 if( len > 0){ 6 //なんか受信後処理をここに書く 7 8 ReadAsync(buffer); //受信処理が終わったので読み込み 9 } 10 } 11 12//ダブルバッファリング 13byte[] buffer1 = new byte[4096]; 14byte[] buffer2 = new byte[4096]; 15void start() 16{ 17 ReadAsync(buffer1); 18 ReadAsync(buffer2); 19}

投稿2022/10/13 04:41

編集2022/10/13 14:30
matukeso

総合スコア1425

良いと思った回答にはグッドを送りましょう。
グッドが多くついた回答ほどページの上位に表示されるので、他の人が素晴らしい回答を見つけやすくなります。

下記のような回答は推奨されていません。

  • 間違っている回答
  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

pitagora

2022/10/13 10:00

ありがとうございます。 専用スレッドというのはTaskで起動できるようなものではなく、Threadを用いるようなものでしょうか。 読み続ける簡単なコード例があればありがたいです。

0

こういうことをするなら、受信バッファをもっと大きな値にしておいて、他の処理が走って受信処理ができない間のデータをバッファで貯めるようにする必要があります、が、あなたが目指しているリアルタイムの処理はできません

そもそも最初からムリなことをさせようとしてます。

投稿2022/10/13 03:43

y_waiwai

総合スコア86013

良いと思った回答にはグッドを送りましょう。
グッドが多くついた回答ほどページの上位に表示されるので、他の人が素晴らしい回答を見つけやすくなります。

下記のような回答は推奨されていません。

  • 間違っている回答
  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

pitagora

2022/10/13 09:58

曖昧な表現にしか見えないのですが・・・具体的に説明していただけないでしょうか。

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
86.12%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問

同じタグがついた質問を見る

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。