Serial Port 系列(11) 基本篇 -- 利用執行緒讀取資料

在邊寫這系列文邊和朋友們討論的過程中,璉大跟我提了一個想法『不要使用 DataReceived 事件,而自行用執行緒來檢查緩衝區』,這其實很類似在Socket 程式中利用Thread加上同步接收來達成非同步效果的方法,這篇就來討論如何來完成這樣的功能。

       在邊寫這系列文邊和朋友們討論的過程中,璉大跟我提了一個想法『不要使用 DataReceived 事件,而自行用執行緒來檢查緩衝區』,這其實很類似在Socket 程式中利用Thread加上同步接收的方法,這篇就來討論如何來完成這樣的功能。

 

       做法其實滿簡單的,只是產生一個執行緒在其中執行一個迴圈 (DoReceive 方法),在迴圈內檢查接收緩衝區是否有資料,如果有就執行 Read 方法接收,沒有資料則跳過。


Imports System.IO.Ports
Imports System.Threading
Public Class Form1
	Private receiving As Boolean
	Private comport As SerialPort
	Private totalLength As Int32 = 0
	Private t As Thread
	Private Delegate Sub Display(ByVal buffer As Byte())

	Private Sub DisplayText(ByVal buffer As Byte())
		TextBox1.Text &= String.Format("{0}{1}", BitConverter.ToString(buffer), Environment.NewLine)
		totalLength = totalLength + buffer.Length
		Label2.Text = totalLength.ToString()
	End Sub
	Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
		comport = New SerialPort("COM5", 9600, Parity.None, 8, StopBits.One)
		If comport.IsOpen = False Then
			comport.Open()
			receiving = True
			t = New Thread(AddressOf DoReceive)
			t.IsBackground = True
			t.Start()
		End If
		
	End Sub

	Private Sub DoReceive()
		Dim buffer(1023) As Byte
		While receiving = True
			If comport.BytesToRead > 0 Then
				Dim length As Int32 = comport.Read(buffer, 0, buffer.Length)
				Array.Resize(buffer, length)
				Dim d As New Display(AddressOf DisplayText)
				Me.Invoke(d, New Object() {buffer})
				Array.Resize(buffer, 1024)
			End If
			Thread.Sleep(16)
		End While
	End Sub

	Private Sub Button_ClearTextbox_Click(sender As System.Object, e As System.EventArgs) Handles Button_ClearTextbox.Click
		TextBox1.Clear()
	End Sub

	Private Sub Button_AbortThread_Click(sender As System.Object, e As System.EventArgs) Handles Button_AbortThread.Click
		receiving = False
	End Sub
End Class

 

 


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
using System.Threading;
namespace ThreadReceiveCS
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();
		}
		private Boolean receiving;
		private SerialPort comport;
		private Int32 totalLength = 0;
		private Thread t;
		delegate void Display(Byte[] buffer);

		private void DisplayText(Byte[] buffer)
		{
			TextBox1.Text += String.Format("{0}{1}", BitConverter.ToString(buffer), Environment.NewLine);
			totalLength = totalLength + buffer.Length;
			Label2.Text = totalLength.ToString();
		}

		private void Form1_Load(object sender, EventArgs e)
		{
			comport = new SerialPort("COM5", 9600, Parity.None, 8, StopBits.One);
			if (!comport.IsOpen)
			{
				comport.Open();
				receiving = true;
				t = new Thread(DoReceive);
				t.IsBackground = true;
				t.Start();
			}
		}

		private void DoReceive()
		{
			Byte[] buffer = new Byte[1024];
			while (receiving)
			{
				if (comport.BytesToRead > 0)
				{
					Int32 length = comport.Read(buffer, 0, buffer.Length);
					Array.Resize(ref buffer, length);
					Display d = new Display(DisplayText);
					this.Invoke(d, new Object[] { buffer });
					Array.Resize(ref buffer, 1024);
				}
				Thread.Sleep(16);
			}
		}

		private void Button_ClearTextbox_Click(object sender, EventArgs e)
		{
			TextBox1.Clear();
		}

		private void Button_AbortThread_Click(object sender, EventArgs e)
		{
			receiving = false;
		}
	}
}

 

       在類別內全域宣告的部份,有一個型別為Boolean的receiving變數,這個變數的功能是為了能讓DoReceive方法內的While迴圈停止;另外有一個Thread型別的變數t,則是為了產生執行緒所宣告。接著來瞭解一下DoReceive方法。

 

 


	Private Sub DoReceive()
		Dim buffer(1023) As Byte
		While receiving = True
			If comport.BytesToRead > 0 Then
				Dim length As Int32 = comport.Read(buffer, 0, buffer.Length)
				Array.Resize(buffer, length)
				Dim d As New Display(AddressOf DisplayText)
				Me.Invoke(d, New Object() {buffer})
				Array.Resize(buffer, 1024)
			End If
			Thread.Sleep(16)
		End While
	End Sub

 

 


		private void DoReceive()
		{
			Byte[] buffer = new Byte[1024];
			while (receiving)
			{
				if (comport.BytesToRead > 0)
				{
					Int32 length = comport.Read(buffer, 0, buffer.Length);
					Array.Resize(ref buffer, length);
					Display d = new Display(DisplayText);
					this.Invoke(d, new Object[] { buffer });
					Array.Resize(ref buffer, 1024);
				}
				Thread.Sleep(16);
			}
		}

 

       在DoReceive方法中的While迴圈條件是當receiving變數值為True的狀態下則進入迴圈,接著使用SerialPort.BytesToRead屬性檢查接收緩衝區內是否有資料,如果有資料則開始使用SerialPort.Read方法接收資料後顯示在畫面的控制項中;在每一次的迴圈最後使用 Thread.Sleep方法讓執行緒稍微休息一下,在無限或長迴圈中使用Thread.Sleep是一個可以讓資源較有機會釋出的小技巧。

 

       在Form.Load事件委派函式中做一點修改,使其產生執行緒來執行自訂的DoReceive方法。


	Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
		comport = New SerialPort("COM5", 9600, Parity.None, 8, StopBits.One)
		If comport.IsOpen = False Then
			comport.Open()
			receiving = True
			t = New Thread(AddressOf DoReceive)
			t.IsBackground = True
			t.Start()
		End If
	End Sub

 


		private void Form1_Load(object sender, EventArgs e)
		{
			comport = new SerialPort("COM5", 9600, Parity.None, 8, StopBits.One);
			if (!comport.IsOpen)
			{
				comport.Open();
				receiving = true;
				t = new Thread(DoReceive);
				t.IsBackground = true;
				t.Start();
			}
		}

      

       這也沒有啥新鮮的,關於Thread的使用方式和之前的 [Serial Port 系列(7) 基本篇 -- 建立一個簡單的純發送程式(多緒型範例)] 差不多,如果不明白的話可以回頭看那篇文中的說明。