2015-11-13 319 views
5

我正在修改一個基於C#的UI,它與一個小型PIC微控制器測試設備接口。C#:讀取串行端口 - BytesToRead

UI由兩個按鈕組成,它們通過串口連接向微控制器發送「命令」來啓動測試。每隔250毫秒,用戶界面就會輪詢串行接口,尋找包含PIC測試結果的簡短消息。該消息顯示在文本框中。

我繼承的代碼如下:

try 
{ 
    btr = serialPort1.BytesToRead; 
    if (btr > 0) 
     Thread.Sleep(300); 
    btr = serialPort1.BytesToRead; 
    if (btr > 0) 
    { 
     Thread.Sleep(300); 
     btr = serialPort1.BytesToRead; 

     numbytes = serialPort1.Read(stuffchar, 0, btr); 
     for (x = 0; x < (numbytes); x++) 
     { 
      cc = (stuffchar[x]); 
      stuff += Convert.ToString(Convert.ToChar((stuffchar[x]))); 
     } 

什麼將是第幾行由三個調用BytesToRead和兩個300毫秒睡眠電話前最後讀串口的原理是什麼?除非我錯誤地解釋代碼,否則從串口讀取任何成功消息都將花費超過600毫秒,這對我來說似乎很奇怪。

回答

0

如果該代碼最初處於循環狀態,它可能是一種等待PIC收集數據的方式。

如果你有真正的硬件測試我會sugest你刪除睡覺。

4

這是對SerialPort.Read()的行爲的一個可怕的破解。它只返回實際接收的字節數。通常只有1或2個,串行端口速度很慢,現代PC速度非常快。所以通過調用Thread.Sleep(),代碼延遲了UI線程足夠長的時間以使Read()調用返回更多字節。無論協議是什麼樣子,希望它們都可以。通常工作,並不總是。在發佈的代碼中,它不起作用,程序員只是任意拖延了兩次。啊。

當然,最大的不幸是UI線程在強迫睡眠時非常緊張。非常明顯,它的繪製速度非常緩慢並且對用戶輸入做出響應。

這需要通過首先注意協議來修復。 PIC需要在其響應中發送固定數量的字節,因此您可以簡單地對它們進行計數,或者讓PC檢測是否收到完整的響應。通常通過發送一個唯一的字節作爲響應的最後一個字節(SerialPort.NewLine),或者通過在響應開始時將響應的長度作爲一個字節值包含在內。具體的建議很難給出,你根本沒有描述協議。

您可以保留hacky代碼並將其移至工作線程中,以免影響用戶界面如此糟糕。您可以從SerialPort.DataReceived事件中免費獲得一個。但這往往會產生兩個問題,而不是解決核心問題。

0

@TomWr你是對的,從我正在閱讀的是這種情況。
你下面有我的評語摘錄:

try 
{ 
    // Let's check how many bytes are available on the Serial Port 
    btr = serialPort1.BytesToRead; 

    // Something? Alright then, let's wait 300 ms. 
    if (btr > 0) 
     Thread.Sleep(300); 

    // Let's check again that there are some bytes available the Serial Port 
    btr = serialPort1.BytesToRead; 

    // ... and if so wait (maybe again) for 300 ms 
    // Please note that, at that point can be all cumulated about 600ms 
    // (if we actually already waited previously) 
    if (btr > 0) 
    { 
     Thread.Sleep(300); 
     btr = serialPort1.BytesToRead; 

     numbytes = serialPort1.Read(stuffchar, 0, btr); 
     for (x = 0; x < (numbytes); x++) 
     { 
      // Seems like a useless overhead could directly use 
      // an Encoding and ReadExisting method() of the SerialPort. 
      cc = (stuffchar[x]); 
      stuff += Convert.ToString(Convert.ToChar((stuffchar[x]))); 
     } 

我的猜測是同它上面通過idstam已經述及,基本上可能要檢查是否是數據由設備發送和獲取它們

你可以用適當的SerialPort方法輕鬆重構這段代碼,實際上有更好更簡潔的方法來檢查串口上是否有數據可用。

而不是「我正在檢查端口上有多少字節,如果有什麼,然後我等待300毫秒,然後再次相同的事情。「這很可悲地結束於
」所以是2倍300毫秒= 600毫秒,或只是一次(取決於是否有第一次「,或者什麼也沒有(取決於你通過這個用戶界面進行通信的設備)因爲Thread.Sleep會阻止用戶界面......)。「

首先讓我們考慮一段時間,您試圖保持儘可能多的相同的代碼庫,爲什麼不等待600ms?

或者爲什麼不只是使用ReadTimeout屬性和捕捉超時異常,不是那麼幹淨,但至少在可讀性方面更好,而且您直接獲取字符串而不是使用一些Convert.ToChar()調用...

我感覺代碼已經從C或C++(或者至少是背後的原理)移植到了主要是嵌入式軟件背景的人身上。

無論如何,回到可用字節檢查的數量,我的意思是除非串行端口數據在另一個線程/ BackgroundWorker/Task處理程序中刷新,否則我沒有看到任何檢查它的兩個原因,特別是以它的方式編碼。
爲了讓它更快?不是真的,如果串行端口上實際存在數據,則會有額外延遲。這對我來說沒有多大意義。

使您的代碼片段稍微好一點的另一種方法是使用ReadExisting()進行輪詢。

否則,您也可以考慮使用SerialPort BaseStream的異步方法。

總而言之,如果不訪問代碼庫的其餘部分,即上下文,很難說。

如果您有更多關於目標/協議的信息,它可以提供一些關於如何做的提示。否則,我可以說這似乎是編碼不好,再一次,脫節。

我甚至把漢斯提到的關於用戶界面響應的問題翻了一番,因爲我真的希望你的代碼片段在一個不是用戶界面的線程中運行(儘管你在帖子中提到用戶界面正在輪詢我仍然希望該片段是爲另一名工人)。

如果這確實是UI線程,那麼每次有Thread.Sleep調用都會阻止它,這會導致UI不能真正響應用戶交互,並且可能會給最終用戶帶來一些挫折感。

可能還需要訂閱DataReceived事件並執行處理程序所需的操作(例如,使用緩衝區和比較值等)。

請注意mono還沒有實現這個事件的觸發器,但是如果你正在運行一個簡單的MS .NET實現,這是完美的沒有多線程的麻煩。

簡而言之:

  • 檢查哪個線程(S)爲被照顧您的片斷和心靈有關的UI響應
    • 如果是UI,然後通過使用另一個線程線程,BackgroundWorker(線程池)或任務。
    • 流異步方法,以避免UI線程同步的
  • 嘗試的麻煩,看目標是否真的值得雙300毫秒線程睡眠方法調用
  • ,您可以直接獲取字符串代替如果後者檢查正在使用以執行操作而不是自己收集字節(如果所選編碼可以滿足您的需要),則可以採集數據。
+0

流異步方法(例如'port.BaseStream.ReadAsync')不使用另一個線程....這是件好事。您不必擔心從多個線程同步對數據結構的訪問,或者在線程之間編組UI。每當事件處理程序返回或調用「await」時,只需將數據結構保持一致即可。 –

+0

@BenVoigt http://referencesource.microsoft.com/#mscorlib/system/io/stream.cs,e224b4bec8748849 async/await正在使用Task,所以Threadpool。因此它可以在另一個線程上運行,但不一定。 如果你認爲我錯了,隨意解釋一下。 同意特別是當它與用戶界面相關時。 – Ehouarn

+1

任務對象默認在當前上下文中運行,在UI應用程序中,默認上下文是UI消息分派器。沒有使用額外的線程。現在,您可以選擇將任務強制爲工作線程,但這對於I/O尤其是串行I/O來說很愚蠢。工作線程僅對CPU綁定操作有利。 –