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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

シリアルポート

シリアルポートは一度に一ビットごと移行される物理的なインターフェイスです。一般的には、9ピンのd-subコネクタであるRS-232を指します。

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

マルチスレッド

マルチスレッドは、どのように機能がコンピュータによって実行したのかを、(一般的にはスレッドとして参照される)実行の複合的な共同作用するストリームへ区分することが出来ます。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Q&A

解決済

2回答

13700閲覧

シリアル通信でマルチスレッド化(同時受信データを処理したい)

entaro12345

総合スコア75

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

シリアルポート

シリアルポートは一度に一ビットごと移行される物理的なインターフェイスです。一般的には、9ピンのd-subコネクタであるRS-232を指します。

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

マルチスレッド

マルチスレッドは、どのように機能がコンピュータによって実行したのかを、(一般的にはスレッドとして参照される)実行の複合的な共同作用するストリームへ区分することが出来ます。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

0グッド

0クリップ

投稿2020/09/15 03:02

前提・実現したいこと

以前も似たようなご質問をさせていただきましたが、再度質問させていただきます。
以前の質問

C#でUSB接続でシリアル通信を行うWindowsアプリを作成しています。
同時に複数のデータが来た場合に対応するために、マルチスレッドに変更したいと思っております。
が、恥ずかしながらマルチスレッドの実装を行ったことがなく、ググってもイマイチだったため、
皆さまのお力を借りたいと思いました。
現状は、データが2回来て正常なデータとするため、同時に来た場合にさばけない状態となっています。
(先勝ちな状態です。)

データの一連の流れは、下記になります。
①データ受信[1回目]
②データ受信[2回目] ← ここでデータ確定
③受信データをAPI経由でDBへ格納
④戻り値を受信元へ送信

該当のソースコード

C#

1using Newtonsoft.Json; 2using Newtonsoft.Json.Linq; 3using System; 4using System.Collections.Generic; 5using System.IO; 6using System.IO.Compression; 7using System.IO.Ports; 8using System.Net.Http; 9using System.Reflection; 10using System.Text; 11using System.Threading.Tasks; 12using System.Windows.Forms; 13using System.Xml.Linq; 14 15namespace App 16{ 17 public partial class Form : Form 18 { 19 private SerialPort serialPort; 20 21 // APIパス 22 private const string API_PATH = @"http://XXXXX"; 23 24 private int iRetry = 1; 25 private int iSeq = 0; 26 private string sBefData = string.Empty; 27 private string sData = string.Empty; 28 private string sVal1 = string.Empty; 29 private string[] sReturnVal = new string[2]; 30 private bool bFirst = true; 31 32 readonly string sPath = AppDomain.CurrentDomain.BaseDirectory; 33 private string sFilePathBefore; 34 string sLogFolder = "LogFile"; 35 private bool bSuccess = false; 36 37 public Form() 38 { 39 InitializeComponent(); 40 } 41 42 /// <summary> 43 /// フォームロード時イベント 44 /// </summary> 45 /// <param name="sender"></param> 46 /// <param name="e"></param> 47 private void Form_Load(object sender, EventArgs e) 48 { 49 string[] ports = SerialPort.GetPortNames(); 50 foreach (string port in ports) 51 { 52 this.cmbSerials.Items.Add(port); 53 } 54 if (0 < this.cmbSerials.Items.Count) 55 { 56 this.cmbSerials.SelectedIndex = 0; 57 } 58 } 59 60 /// <summary> 61 /// 接続ボタン押下時イベント 62 /// </summary> 63 /// <param name="sender"></param> 64 /// <param name="e"></param> 65 private void btnConnect_clicked(object sender, EventArgs e) 66 { 67 if (this.cmbSerials.SelectedItem != null && this.serialPort == null) 68 { 69 // COMポートをOPEN 70 serialPort = new SerialPort(); 71 serialPort.DataReceived += new SerialDataReceivedEventHandler(SerialPort1_DataReceived); 72 serialPort.PortName = this.cmbSerials.SelectedItem.ToString(); 73 serialPort.BaudRate = 19200; 74 serialPort.DataBits = 8; 75 serialPort.Parity = Parity.None; 76 serialPort.StopBits = StopBits.One; 77 serialPort.Encoding = Encoding.UTF8; 78 79 try 80 { 81 serialPort.Open(); 82 } 83 catch (Exception ex) 84 { 85 } 86 } 87 } 88 89 /// <summary> 90 /// データ受信時イベント 91 /// </summary> 92 /// <param name="sender"></param> 93 /// <param name="e"></param> 94 private void SerialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e) 95 { 96 if (serialPort != null && serialPort.IsOpen) 97 { 98 try 99 { 100 // 受信データを読み込む 101 SerialPort sp = (SerialPort)sender; 102 string sChildData = sp.ReadExisting(); 103 104 if (sChildData.Length == 2 && bFisrst) 105 { 106 sVal1 = sChildData; 107 } 108 else 109 { 110 sData = sChildData; 111 if (bFirst) { 112 return; 113 } 114 } 115 116 if (!string.IsNullOrEmpty(sData) && !string.IsNullOrEmpty(sVal1)) 117 { 118 if (0 < sData.Length) { 119 if (string.IsNullOrEmpty(sBefData) || sBefData != sData) 120 { 121 var parameters = new Dictionary<string, string>() 122 { 123 // APIへ渡すデータセット 124 }; 125 var content = new FormUrlEncodedContent(parameters); 126 127 try { 128 sReturnVal = GetResponseAPI(API_PATH, content); 129 sBefData = sData; 130 bSuccess = true; 131 } catch { 132 bSuccess = false; 133 } 134 135 if (bSuccess) 136 { 137 if (0 < sReturnVal.Length) 138 { 139 try 140 { 141 // 送信元へデータ送信 142 } 143 catch (Exception ex) 144 { 145 } 146 } 147 else 148 { 149 this.iRetry = 1; 150 } 151 } 152 sData = string.Empty; 153 sVal1 = string.Empty; 154 sReturnVal = new string[]{ }; 155 } 156 } 157 } 158 } 159 catch (Exception ex) 160 { 161 } 162 } 163 } 164 165 /// <summary> 166 /// 終了ボタン押下時イベント 167 /// </summary> 168 /// <param name="sender"></param> 169 /// <param name="e"></param> 170 private void btnFinish_clicked(object sender, EventArgs e) 171 { 172 if (serialPort != null && serialPort.IsOpen) 173 { 174 serialPort.Close(); 175 serialPort = null; 176 } 177 } 178 179 /// <summary> 180 /// APIをたたいてレスポンスを取得 181 /// </summary> 182 private string[] GetResponseAPI(string sApiUrl, FormUrlEncodedContent sContent) 183 { 184 string[] sVal = new string[2]; 185 var task = Task.Run(() => { 186 return HttpPost(sApiUrl, sContent); 187 }); 188 string response = task.Result; 189 JArray data = (JArray)JsonConvert.DeserializeObject(response); 190 foreach (JObject obj in data) 191 { 192 JValue did = (JValue)obj["device_id"]; 193 JValue val = (JValue)obj["return_data"]; 194 sVal[0] = (string)did.Value; 195 sVal[1] = (string)val.Value; 196 } 197 198 return sVal; 199 } 200 201 /// <summary> 202 /// APIをPOSTする 203 /// </summary> 204 /// <param name="sUrl">APIのURL</param> 205 /// <param name="fContent">引数をエンコードしたもの</param> 206 /// <returns></returns> 207 async public Task<string> HttpPost(string sUrl, FormUrlEncodedContent fContent) 208 { 209 var client = new HttpClient(); 210 var response = await client.PostAsync(sUrl, fContent); 211 return await response.Content.ReadAsStringAsync(); 212 } 213 } 214} 215

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

Windows 10
Visual Studio 2017
c#/Windows Form/.Net Framewrok 4.7.1

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

マルチスレッドが解決になるのでしょうか...

いまのプログラムでは、すべてのシリアルポートの受信イベントをひとつのハンドラで受け取っていて、しかもハンドラの中で「誰から来たデータか」を判別しないまま処理しているように思えます。つまり、全部ごっちゃにしてしまっているのでうまくいっていない、とすると、それをマルチスレッドにしても何も解決しないのでは。

私の趣味的には、シリアルポート毎にハンドラを個別に持たせて、それぞれのポートはそれぞれに処理させるかな。もちろん、ひとつのハンドラで、sender毎に処理を切り替えてもいいと思いますけれど。

それと、ハンドラは送信データに対して「いつ呼ばれるかわからない」ものです。極論、1回目の送信の途中で呼ばれるかも(1バイトでも受信すればハンドラは呼ばれるかも)、逆に2回送信が完了してしまってから呼ばれるかも(いつまでに呼ばれる、という期限もありません)、そのどんな場合でも正しくデータを得て、2回送信されたことの確認ができるようにプログラムを作らなきゃいけません。
そうなっているでしょうか。


サンプル書いてみました。シリアル周りだけ、エラーとかは無視。
全然マルチスレッドでないのは、解決したい内容にマルチスレッドが全く寄与しないためです。

C#

1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Text; 5using System.Threading.Tasks; 6using System.IO.Ports; 7 8namespace SerialMulti 9{ 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 object lockObj = new object(); 15 //シリアルポート毎にインスタンスを生成する 16 Connection con1 = new Connection(args[0], lockObj); 17 Connection con2 = new Connection(args[1], lockObj); 18 19 string timeStamp = DateTime.Now.ToString("mm.ss.fff"); 20 Console.WriteLine("Start: {0}\n", timeStamp); 21 22 //なにかキーを押したら終了 23 ConsoleKeyInfo tmp = Console.ReadKey(); 24 } 25 } 26 // シリアルポートとハンドラをクラスにまとめておく 27 class Connection 28 { 29 SerialPort port = new SerialPort(); 30 char[] buf = new char[4]; 31 object lockObj; 32 33 public Connection(string portName, object lockObj) 34 { 35 this.lockObj = lockObj; 36 port.PortName = portName; 37 port.BaudRate = 115200; 38 port.DataReceived += Port_DataReceived; 39 port.Open(); 40 } 41 ~Connection() 42 { 43 port.Close(); 44 } 45 //インスタンス毎に各ポート「専用」のハンドラとなる 46 private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e) 47 { 48 //受信文字がある限り処理する。 49 //受信文字が空になって一旦ハンドラを抜けても次の文字を受信したら動作が継続できなければいけない 50 while (port.BytesToRead != 0) 51 { 52 //直近4文字のバッファに受信する 53 for (int i = 0; i < buf.Length - 1; i++) 54 { 55 buf[i] = buf[i + 1]; 56 } 57 buf[buf.Length - 1] = (char)port.ReadByte(); 58 //直近4文字において前半2文字と後半2文字が同じか検査 59 if (buf[0] == buf[2] && buf[1] == buf[3]) 60 { 61 string timeStamp = DateTime.Now.ToString("mm.ss.fff"); 62 //念の為排他処理にしておく 63 lock (lockObj) 64 { 65 Console.WriteLine("{0} {1}:{2}{3}", timeStamp, port.PortName, buf[0], buf[1]); 66 } 67 //送り返し 68 port.Write(String.Format("{0}{1}", buf[0], buf[1])); 69 } 70 } 71 } 72 73 } 74}

これに、マイコンと、そのUART出力をパラに別のUSB-UART変換を噛ませてPCのポートに入力、つまり「複数のシリアル信号を全く同時に入力」した時の出力がこんな感じです。(USBを通ったりしているうちにプログラムからみて完全に同時とは言えなくなるでしょうが、まぁそこそこ)

text

132.45.494 COM3:BC 232.45.495 COM16:BC 332.45.529 COM3:CD 432.45.543 COM16:CD 532.45.564 COM3:DE 632.45.575 COM16:DE 732.45.599 COM3:EF 832.45.607 COM16:EF 932.45.634 COM3:FG 1032.45.639 COM16:FG 1132.45.669 COM3:GH 1232.45.671 COM16:GH 1332.45.703 COM16:AB 1432.45.704 COM3:AB 1532.45.739 COM3:BC 1632.45.751 COM16:BC 1732.45.774 COM3:CD 1832.45.783 COM16:CD 1932.45.809 COM3:DE 2032.45.815 COM16:DE

32.45.703のあたりなど、微妙なタイミングによってハンドラが呼ばれる順番が変わったりしているのが見えますが、つまりほぼ同時に入力された信号をきちんと処理できているようです。

マイコン側テストプログラムも追記

C++

1//mbed(STM32) 2#include "mbed.h" 3 4int main(void){ 5 Serial pc(USBTX, USBRX); 6 pc.baud(115200); 7 char c='A'; 8 int offset=0; 9 while(1){ 10 printf("%c%c",c+offset,c+offset+1); 11 fflush(0); 12 printf("%c%c",c+offset,c+offset+1); 13 fflush(0); 14 offset=(offset+1)%('H'-'A');// 'A'~'H'を回す 15 wait(40/115.2);//送り返し時間分待ち 16 } 17 return 0; 18}

投稿2020/09/15 04:01

編集2020/09/19 12:14
thkana

総合スコア7639

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

entaro12345

2020/09/15 04:08

回答ありがとうございます。 >シリアルポート毎にハンドラを個別に持たせて… 上記は、イメージ的に下記のような実装という認識で合っていますでしょうか。 serialPort.DataReceived += (s, SerialPort1_DataReceived) => { };
thkana

2020/09/15 12:20

そもそもserialPortの変数が一つしかない、というところで異常なものを感じますがそれはともかく、「ハンドラ(のインスタンス)を、それぞれのシリアルポート用に用意する」ということです。
entaro12345

2020/09/22 23:24

回答ありがとうございます。 理解しました。 いただいたサンプルをもとに修正しましたところいい感じに動きましたので、 経過観察したいと思います。
guest

0

べつにマルチスレッドにしなくても、SerialPortの受信イベントで駆動していけばいいです。

現状は、データが2回来て正常なデータとするため、

これではダメです。
シリアル通信では1文字づつ受信するという場合もあるので、どのような場合でも正常に動作するように配慮する必要があります
一つのメッセージの区切り(ふつうは改行コード)をはっきり決めておいて、その区切りが来たら一つのメッセージとして処理に回す、とう言うことを行いましょう

投稿2020/09/15 03:08

y_waiwai

総合スコア87774

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

entaro12345

2020/09/15 04:22

回答ありがとうございます。 >SerialPortの受信イベントで駆動していけばいい というのは、具体的にどう処理を変えればよいのでしょうか? 理解できずすみません。
y_waiwai

2020/09/15 04:40

複数のシリアルポートを使おうが、それぞれ個別の受信イベントを使えば、さばけない状態はありえません。 これはマルチスレッドにしようがどうしようが関係ないです。 まずは、あなたの言うさばけない状態というコードを組んでみてはどうでしょう
entaro12345

2020/09/15 04:55

ありがとうございます。 現時点ソースで、すでに複数データがほぼ同時に受信した場合に、DBにAPI経由で送信できていないデータがあります。
y_waiwai

2020/09/15 04:57

一つのポートで複数受信ですか? それではマルチスレッドにしようが解決できません。
entaro12345

2020/09/15 05:19

一つのシリアルポートで複数データ受信になります。 PC接続のシリアルポートを親とすると子が大量にいるイメージです。
y_waiwai

2020/09/15 05:22

シリアルポートってのは1対1の通信方式です。 基本的に、そもそもの設計がわるいですね 普通そういう場合は、通信プロトコルを厳密に決めて、親からの指示で子の一つを選択し、通信を行うという形式を取り、絶対に同時の通信を行わないようにします。
entaro12345

2020/09/15 05:38

なるほど。 1 対 多 はよろしくないんですね。 となると、今回のケースでいうと親と子の数が同数必要になるということですね。 勉強になります。 ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問