Serial Port 系列(10) 基本篇 -- DataReceived Event 簡易應對方針

在使用DataReceived事件的時候要避免執行緒暴衝的狀況有幾個簡單的方式可以避免。

       在使用DataReceived事件的時候要避免執行緒暴衝的狀況有幾個簡單的方式可以避免。

 

       在讀取前檢查 BytesToRead屬性值

       SerialPort類別有一個 [SerialPort.BytesToRead 屬性] 可以檢查目前在讀取緩衝區內的位元組數,所以我們可以利用此屬性先檢查緩衝區是否有資料可供讀取,如果有的話才呼叫 Read 方法,藉此避免在緩衝區被清空的狀態下執行Read。我們可以把之前在 [Serial Port 系列(8) 基本篇 -- 使用DataReceived 事件接收資料] 中的 comport_DataReceived 方法內容改成以下的程式碼。


	Private Sub comport_DataReceived(sender As Object, e As SerialDataReceivedEventArgs)
		Dim buffer(1023) As Byte
		If DirectCast(sender, SerialPort).BytesToRead > 0 Then
			Dim length As Int32 = DirectCast(sender, SerialPort).Read(buffer, 0, buffer.Length)
			Array.Resize(buffer, length)
			Dim d As New Display(AddressOf DisplayText)
			Me.Invoke(d, New Object() {buffer})
		End If
	End Sub

 


		private void comport_DataReceived(Object sender, SerialDataReceivedEventArgs e)
		{
			if ((sender as SerialPort).BytesToRead > 0)
			{
				Byte[] buffer = new Byte[1024];
				Int32 length = (sender as SerialPort).Read(buffer, 0, buffer.Length);
				Array.Resize(ref buffer, length);
				Display d = new Display(DisplayText);
				this.Invoke(d, new Object[] { buffer });
			}
		}

 

       設定ReadTimeout屬性

       設定[SerialPort.ReadTimeout 屬性] 也是一個可以使用的方式,在沒有設定此屬性的情況,當程式執行到 SerialPort.Read時執行緒會封鎖在這個位置直到讀取資料完成為止;一旦有指定非InfiniteTimeout的數值時,在指定時間內沒有讀取到任何資料就會發生[TimeoutException] ,因此就可以在程式中攔截此例外狀況並加以處理。雖然我在測試BytesToRead的過程中並未發現有例外產生的現象,但我也不敢百分之百的確定萬無一失,所以這個方式等於是再加一層保險。

 

       所以這要修改兩個地方,一是在建立執行個體後設定屬性


	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)
		comport.ReadTimeout = 2000
		AddHandler comport.DataReceived, AddressOf comport_DataReceived
		If comport.IsOpen = False Then
			comport.Open()
		End If
	End Sub

 


		private void Form1_Load(object sender, EventArgs e)
		{
			comport = new SerialPort("COM5", 9600, Parity.None, 8, StopBits.One);
			comport.ReadTimeout = 2000;
			comport.DataReceived += new SerialDataReceivedEventHandler(comport_DataReceived);
			if (!comport.IsOpen)
			{
				comport.Open();
			}
		}

 

       第二則是在DataReceived Event委派函式中攔截Timeout


	Private Sub comport_DataReceived(sender As Object, e As SerialDataReceivedEventArgs)
		Dim buffer(1023) As Byte
		If DirectCast(sender, SerialPort).BytesToRead > 0 Then
			Try
				Dim length As Int32 = DirectCast(sender, SerialPort).Read(buffer, 0, buffer.Length)
				Array.Resize(buffer, length)
				Dim d As New Display(AddressOf DisplayText)
				Me.Invoke(d, New Object() {buffer})
			Catch timeoutEx As TimeoutException
				'以下這邊請自行撰寫你想要的例外處理
			Catch ex As Exception
				'這邊請自行撰寫你想要的例外處理
			End Try
		End If
	End Sub

 


		private void comport_DataReceived(Object sender, SerialDataReceivedEventArgs e)
		{
			if ((sender as SerialPort).BytesToRead > 0)
			{
				try
				{
					Byte[] buffer = new Byte[1024];
					Int32 length = (sender as SerialPort).Read(buffer, 0, buffer.Length);
					Array.Resize(ref buffer, length);
					Display d = new Display(DisplayText);
					this.Invoke(d, new Object[] { buffer });
				}
				catch (TimeoutException timeoutEx)
				{
					//以下這邊請自行撰寫你想要的例外處理
				}
				catch (Exception ex)
				{
					//以下這邊請自行撰寫你想要的例外處理
				}
			}
		}

 

       這兩個方式我大概測試了幾次是沒發生什麼問題,如果有使用這方式還產生執行緒暴衝的情形還請回覆讓我知道一下。