「Pythonで非同期処理(asyncio)」でPythonの非同期処理を行いましたが、C#でも同様にTaskクラスを使って非同期処理が行えます。今回はWindowsのGUI「Formクラス」を使ってボタンによりシリアル通信の受信待ちを非同期処理により行い、受信待ちの間も他のボタンに対して応答できることを確認します。
シリアル通信の非同期処理画面
C#で作成した非同期処理画面を次に示します。
非同期処理画面の各ボタン等の機能を次に示します。
- 接続ボタン
- シリアル転送の開始
- 切断ボタン
- シリアル転送の切断
- ボタン
- テキストボックスのクリア
- テキストボックス
- シリアル転送で受信したデータ表示
C#による非同期処理プログラムの作成
プロジェクト名は「taskDemo」としました。シリアル転送の受信では、データ受信イベント「ReceivedBytesThreshold」を使って、受信したデータを「ReadExisting」メソッドで取得し、Invoke()を使用してスレッドを切り替え、formクラスのメソッドに受信データを渡す方法がありますが、今回の非同期処理では、次のようにasync/awaitとTask.Runメソッドを使用します。
- 非同期にしたい処理をTask.Runメソッドの引数にデリーゲートとして指定します。
- Task.Runメソッドにawaitキーワードを付けて、非同期処理が終わるまで後続の処理を待機するようにします。今回のプログラムでは、95行目「readserial」メソッドの呼び出しでシリアル通信の受信関数の呼び出しを非同期処理としています。したがってシリアル通信で受信データが完了しない限り95行目以降は実行されません。
- await付きのメソッドを含むメソッドにはasyncキーワードを付けて、戻り値をTask型にします。今回のプログラムでは、「btnconnect_Click」メソッドにasyncキーワードを付けます。95行目「readserial」メソッドの呼び出しをawait付きのTaskとしているためです。
- 101行目ではテキストボックスに受信したデータを表示させていますが、この処理を行うのはシリアル通信でデータを受信した後になります。
- 「button_Click」メソッドは、ボタンを押すごとに制御が移り、コンソールへ「xxxxx」の表示とテキストボックスの内容をクリアします。これにより、シリアル通信による受信待ち時でも、ボタン操作ができることを確認します。
なお、非同期処理を確認するために次のステートメントを追加します。
- 「Thread.CurrentThread.ManagedThreadId」メソッドにより実行中のスレッドのIDを表示し、スレッドの変化を確認します。
- 「Console.Write」メソッドにより動作部位を表示させ、シリアル通信の受信待ちを確認します。
namespace { public partial class Form1 : Form { SerialPort myPort; int read = 0; int len = 0; byte[] data; public Form1() { InitializeComponent(); } public void openserial(string PortName) { int id = System.Threading.Thread.CurrentThread.ManagedThreadId; Console.WriteLine("openserial ThreadID : " + id); int BaudRate = 115200; Parity Parity = Parity.None; int DataBits = 8; StopBits StopBits = StopBits.One; Console.Write("openserial1\n"); myPort = new SerialPort(PortName, BaudRate, Parity, DataBits, StopBits); Console.Write("openserial2\n"); try { myPort.Open(); } catch (Exception ex) { MessageBox.Show(ex.Message); } } public void readserial() { int id = System.Threading.Thread.CurrentThread.ManagedThreadId; Console.WriteLine("readserial ThreadID : " + id); int length; try { Console.Write("readseria1l\n"); len = 9; data = new byte[len]; read = 0; while (read < len) { Console.Write("readserial2\n"); length = myPort.Read(data, read, len - read); read += length; Console.Write("readserial3 {0} {1}\n", length, read); } } catch (Exception ex) { MessageBox.Show(ex.Message); } } public void closeserial() { int id = System.Threading.Thread.CurrentThread.ManagedThreadId; Console.WriteLine("closeserial ThreadID : " + id); if (myPort.IsOpen == true) { myPort.Close(); } } private void button_Click(object sender, EventArgs e) { Console.WriteLine("xxxxx"); tbxindata.Text = ""; } private async void btnconnect_Click(object sender, EventArgs e) { try { int id = System.Threading.Thread.CurrentThread.ManagedThreadId; Console.WriteLine("btnconnect_Click1 ThreadID : " + id); Console.Write("btnconnect_Click1\n"); openserial("COM10"); await Task.Run(() => readserial()); Console.Write("btnconnect_Click2\n"); id = System.Threading.Thread.CurrentThread.ManagedThreadId; Console.WriteLine("btnconnect_Click2 ThreadID : " + id); tbxindata.Text = "readserial len: " + read + " data: " + BitConverter.ToString(data).Replace("-", string.Empty) + "\n"; } catch (Exception ex) { MessageBox.Show(ex.Message); } } private void btndisconnect_Click(object sender, EventArgs e) { closeserial(); } } }
C#による非同期処理プログラムの実行
なおシリアル通信には、「com0comによるシリアル接続の試験環境の構築」により、2つの仮想のCOMポートをクロスケーブルで接続したように見せかけ、片方の仮想COMポートからもう一方の仮想COMポートへ通信させました。
com0comによりシリアルデータを送信すると、非同期処理画面のテキストボックスに送信したデータが表示されました。
「Console.Write」メソッドにより表示されたメッセージを次に示します。シリアルデータの入力メソッド「readserial」ではスレッドIDが「6」になっており、別のスレッドで実行されていることがわかります。また、「btnconnect_Click2」表示が、シリアルデータの入力メソッド「readserial」の実行完了「readserial3 9 9」後になっており、処理が待たされたことが確認できます。
btnconnect_Click1 ThreadID : 10 btnconnect_Click1 openserial ThreadID : 10 openserial1 openserial2 readserial ThreadID : 6 readseria1l readserial2 readserial3 9 9 btnconnect_Click2 btnconnect_Click2 ThreadID : 10
シリアル通信ポートのクローズすると次の例外が発生し、メッセージボックスが表示されます。OKボタンを押すと、awaitキーワードにより待たせていた処理が実行されます。