none
使用Thread讀取Serial Port速度很慢,如何改進效能? RRS feed

  • 問題

  • 目前我使用自己建立的Class: RS232, 利用Win32 API對Serial Port做存取。

    我在Thread中,存取Serial Port,並且將讀到的資訊show在UI上面的DataGridView。

    但是單純這樣的for Loop卻跑很慢,是因為要更新UI的關係嗎?

    我利用PortMon監看Serial Port的傳輸,不是很快很順暢。

    同樣的動作我用C++ Builder使用單一Thread針對Serial Port存取約需4分鐘,包含更新UI。

    而使用C#卻要30分鐘以上。

    在以下的程式碼中,進行上述測試的條件是: page=511 , addr=117, datacount=13。

    也就是說第二個for loop總共會對Serial Port讀寫4992次。

    以下是我的Thread code。

    想請教該如何改善效率,會造成時間如此差異的關鍵點在於哪邊呢?

    謝謝

     

     
     private void GetDataThread( )
      {
       tagdatacount = 0;
    			
    			//Update DataGridView
       if (ReaderDataGridView.InvokeRequired)			
        this.Invoke(new ClearAllHandler(ClearRow));
       else
        ReaderDataGridView.Rows.Clear();
    
       byte addr, datacount, tempPage;
    
       int page = 0;
       
       int i;   
    
       for ( i= 128; i < 512; i++)
       {
        if (i > 255)
        {
         tempPage = (byte)(i - 256);
         Data[7] = 1;
        }
        
        else
        {
         tempPage =(byte)i;
         Data[7] = 0;
        }
    				
        Data[0] = 0xAA; 	//0xAA Header
        Data[1] = 1;  	//Reader Address = 1
        Data[2] = 0x04;
        Data[3] = 0;
        Data[4] = 3;  	//Length
        Data[5] = (byte)SetGetCommand.ReadPage;
        Data[6] = tempPage; 		//Page;the
    
        CRC.Word = CRC_CCITT(Data, 8);
        Data[8] = CRC._byte2;
        Data[9] = CRC._byte1;
    
        SerialPort.PurgeComm(rs232.hComm, SerialPort.PURGE_TXABORT | SerialPort.PURGE_TXCLEAR);
        rs232.WritePort(Data,10);
        
    
        SerialPort.PurgeComm(rs232.hComm, SerialPort.PURGE_RXABORT | SerialPort.PURGE_RXCLEAR);
        rs232.ReadPort(11, Data);
    
        if (CRC_CCITT(Data, 11) == 0)
        {
         if (Data[5] == 0)
         {
          page = i;
          break;
         }
        }
        else
        {
         i = i - 1;   // If CRC Check ERROR , repeat the command.
        }
    
       }
       
       if(i==512)
       {
        page = 511;
       }
       
       datacount = Data[7];
       addr = Data[6];
    
    			//Show the value for Debug
       this.Invoke(new UpdateLabelHandler(UpdateLabel), page.ToString(), addr.ToString(), datacount.ToString());
    
       
       if (datacount != 0)
       {
        int EE_page, EE_addr;
        string timestr;
    
        for (EE_page = 128; EE_page <= page; EE_page++)
        {
         if (EE_page == page)
          EE_addr = addr;
         else
          EE_addr = 117;
         for (int j = 0; j < EE_addr; j = j + 9)
         {
    
          if (EE_page > 255)
           Data[8] = 1;
          else
           Data[8] = 0; 
    
          Data[0] = 0xAA; 	//0xAA Header
          Data[1] = 1;  	
          Data[2] = 0x04;
          Data[3] = 1;
          Data[4] = 4;  	
          Data[5] = (byte)SetGetCommand.TagData;
          Data[6] = (byte)EE_page; 		//Page
          Data[7] = (byte)j;      //Addr    
    
          CRC.Word = CRC_CCITT(Data, 9);
    
          Data[9] = CRC._byte2;
          Data[10] = CRC._byte1;
    
          SerialPort.PurgeComm(rs232.hComm, SerialPort.PURGE_TXABORT | SerialPort.PURGE_TXCLEAR);
          rs232.WritePort(Data, 11);
    
         
    
          SerialPort.PurgeComm(rs232.hComm, SerialPort.PURGE_RXABORT | SerialPort.PURGE_RXCLEAR);
          rs232.ReadPort(16, Data);
    
          if ((rs232.BytesRead == 16) & (CRC_CCITT(Data, 16) == 0) & (Data[2] == 0x04))
          {
           //Update ProgressBar
    							if (page < 129)
           {
            if (this.InvokeRequired)
            {
             this.Invoke(new UpdateBarHandler(UpdateBar), 100 / datacount);
             if (j == EE_addr - 9)
              this.Invoke(new UpdateBarHandler(UpdateBar), 100);
            }
            else
             toolStripProgressBar2.Increment(100 / datacount);
           }
    							
           TID._byte2 = Data[5];
           TID._byte1 = Data[6];
           Temperature._byte2 = Data[7];
           Temperature._byte1 = Data[8];
    							
    							//Update DataGridView
           if (ReaderDataGridView.InvokeRequired)
           {
            this.Invoke(new AddRowHandler(AddRow));
            this.Invoke(new UpdateUIHandler(UpdateDataGridView), tagdatacount, 0, (tagdatacount + 1).ToString());
            this.Invoke(new UpdateUIHandler(UpdateDataGridView), tagdatacount, 3, TID.Word.ToString());
            double temp = (double)Temperature.Word / 100;
            this.Invoke(new UpdateUIHandler(UpdateDataGridView), tagdatacount, 4, temp.ToString());
           }
           else
           {
            ReaderDataGridView.Rows.Add(1);
            ReaderDataGridView.Rows[tagdatacount].Cells[0].Value = tagdatacount + 1;
            ReaderDataGridView.Rows[tagdatacount].Cells[3].Value = TID.Word;
            ReaderDataGridView.Rows[tagdatacount].Cells[4].Value = (double)Temperature.Word / 100;
           }
    
           if (((double)Temperature.Word / 100) > (Double.Parse(WarningtextBox.Text)))
           {
            if (ReaderDataGridView.InvokeRequired)
             this.Invoke(new ColorUIHandler(ColorDataGridView), tagdatacount);
            else
             ReaderDataGridView.Rows[tagdatacount].DefaultCellStyle.ForeColor = Color.Red;
           }
    
           Temperature._byte2 = Data[12]; 
           Temperature._byte1 = Data[13];
    
           timestr = "";
           timestr = timestr + Data[9] + ":" + Data[10] + ":" + Data[11];
    							
           if (ReaderDataGridView.InvokeRequired)
           {
            double temp = (double)Temperature.Word / 100;
            this.Invoke(new UpdateUIHandler(UpdateDataGridView), tagdatacount, 6, temp.ToString());
            this.Invoke(new UpdateUIHandler(UpdateDataGridView), tagdatacount, 5, DateTime.Parse(timestr).ToString("HH:mm:ss"));
           }
           else
           {
            ReaderDataGridView.Rows[tagdatacount].Cells[6].Value = (double)Temperature.Word / 100;
            ReaderDataGridView.Rows[tagdatacount].Cells[5].Value = DateTime.Parse(timestr).ToString("HH:mm:ss");
           }
    
           tagdatacount++;
          }
          else
          {
           j = j - 9;    // If CRC Check ERROR , repeat the command.
          }
         }    
    
        }
    
       }
       
    
       
    
       if (tagdatacount != 0)
       {
        MessageBox.Show("讀取完畢", "Finish", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
       }
    
       else
       {
        MessageBox.Show("無資料", "No Data", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
       }
      }
    

     

    2011年3月25日 上午 05:47

解答

  • 建議你先將問題釐清是哪邊吃效能方式如下

    1.將更新UI的code完全移除,測試花費時間
    2.不要建立獨立的Thread也就是不是用InvokeRequired方式測試所花費時間
    3.建立獨立的Thread也使用InvokeRequired方式測試所花費時間

    若1的時間與你C++ Builder接近那代表IO效能不會因為開發工具而有所影響
    若2-1與C++ Builder差異很大,那這可能是.Net的內建DataGrid可能比C++ Builder效能差,這是有可能的,因為.Net的DataGrid功能比較複雜,大量的新增動作可能效能上會不彰.
    若上面都正常,3-1時間遠大於2-1那代表委派呼叫過於頻繁,透過委派呼叫實際上是很耗資源的.而解決方式就是採用批次更新而不減少單一更新方式.

    以上提供你做參考.

     

    • 已標示為解答 Jonathan Hwu 2011年3月28日 上午 06:02
    2011年3月25日 上午 09:26

所有回覆

  • 看起應該慢的原因是在更新UI的部分,因為無法看到你的addrow與UpdateUI那段程式碼,無法知道確切原因.
    2011年3月25日 上午 06:03
  • 您好,以下附上我使用委派更新UI的Code。

    謝謝您的指教。

        private void UpdateDataGridView(int row, int col, string value)
        {
          ReaderDataGridView.Rows[row].Cells[col].Value = value;      
        }
    
        private void ColorDataGridView(int row)
        {
          ReaderDataGridView.Rows[row].DefaultCellStyle.ForeColor = Color.Red;
        }
        private void AddRow()
        {
          ReaderDataGridView.Rows.Add(1);
        }
    
        private void ClearRow()
        {
          ReaderDataGridView.Rows.Clear();
          toolStripProgressBar2.Value = 0;
        }
        private void UpdateBar(int progress)
        {     
          toolStripProgressBar2.Increment(progress);
        }
        private void UpdateLabel(string temp1,string temp2,string temp3)
        {
          label12.Text = temp1;
          label13.Text = temp2;
          label14.Text = temp3;
        }
    
        delegate void UpdateUIHandler(int row, int col, string value);
        delegate void ColorUIHandler(int row);
        delegate void AddRowHandler();
        delegate void ClearAllHandler();
        delegate void UpdateBarHandler(int progress);
        delegate void UpdateLabelHandler(string temp1, string temp2, string temp3);
    

    2011年3月25日 上午 09:10
  • 建議你先將問題釐清是哪邊吃效能方式如下

    1.將更新UI的code完全移除,測試花費時間
    2.不要建立獨立的Thread也就是不是用InvokeRequired方式測試所花費時間
    3.建立獨立的Thread也使用InvokeRequired方式測試所花費時間

    若1的時間與你C++ Builder接近那代表IO效能不會因為開發工具而有所影響
    若2-1與C++ Builder差異很大,那這可能是.Net的內建DataGrid可能比C++ Builder效能差,這是有可能的,因為.Net的DataGrid功能比較複雜,大量的新增動作可能效能上會不彰.
    若上面都正常,3-1時間遠大於2-1那代表委派呼叫過於頻繁,透過委派呼叫實際上是很耗資源的.而解決方式就是採用批次更新而不減少單一更新方式.

    以上提供你做參考.

     

    • 已標示為解答 Jonathan Hwu 2011年3月28日 上午 06:02
    2011年3月25日 上午 09:26
  • 建議你先將問題釐清是哪邊吃效能方式如下

    1.將更新UI的code完全移除,測試花費時間
    2.不要建立獨立的Thread也就是不是用InvokeRequired方式測試所花費時間
    3.建立獨立的Thread也使用InvokeRequired方式測試所花費時間

    若1的時間與你C++ Builder接近那代表IO效能不會因為開發工具而有所影響
    若2-1與C++ Builder差異很大,那這可能是.Net的內建DataGrid可能比C++ Builder效能差,這是有可能的,因為.Net的DataGrid功能比較複雜,大量的新增動作可能效能上會不彰.
    若上面都正常,3-1時間遠大於2-1那代表委派呼叫過於頻繁,透過委派呼叫實際上是很耗資源的.而解決方式就是採用批次更新而不減少單一更新方式.

    以上提供你做參考.

     

    您好,請問您說的批次更新,指的是使用一個delegate去更新所有UI就好嗎?

    我會試試看,感謝您的建議。

    2011年3月25日 上午 10:15
  • 你是怎麼呼叫 GetDataThread 方法的?

     



    ASP.NET 2Share - http://www.dotblogs.com.tw/johnny
    2011年3月25日 上午 11:02
  • 以下僅供參考, 不一定是你問題的真正原因:

    通常我不會直接對DataGridView的Cell做更動, 一般都是將DataGridView的DataSource指向一個DataTable, 然後更動該DataTable的資料.


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。
    2011年3月25日 下午 01:27
    版主
  • 你是怎麼呼叫 GetDataThread 方法的?

     



    ASP.NET 2Share - http://www.dotblogs.com.tw/johnny

    您好,我用以下的Button方法去呼叫。

    private void GetDatabutton_Click(object sender, EventArgs e)
        {
          ThreadStart GetDataRun = new ThreadStart(GetDataThread);
          Thread GetData = new Thread(GetDataRun);
          GetData.Start();
        }
    

    2011年3月28日 上午 02:19
  • 有試過調整 Priority 嗎?

     

     


    ASP.NET 2Share - http://www.dotblogs.com.tw/johnny
    2011年3月28日 上午 02:48
  • 有試過調整 Priority 嗎?

     

     


    ASP.NET 2Share - http://www.dotblogs.com.tw/johnny

    嚐試的結果是:

    1.將更新UI的code拿掉之後,使用Thread與不使用Thread時間都在5分鐘上下。

    2.使用Thread,並且Priority設定為Highest,速度有增加,完成時間在4分20秒左右。

    所以,整個command的流程就需要五分鐘左右,

    3.使用單一Thread執行,不使用委派,直接更新UI,整個流程長達一小時!!!

    我想問題就是更新UI的部份,要如何做才能將更新DataGridView的時間減少?

    目前想到的方法,就是Bill Chung所建議的,將資料填入DataTabel使用DataBinding與DataGridView繫結。

    而programlin所建議的批次更新UI,我也會嚐試看看。減少使用委派的次數,但勢必得使用一塊memory來存放讀進來的Data,

    再更新到UI上了。

    請問,是否之後遇到跨Thread更新UI,資料量大時,都會有這樣的狀況呢?

    之前的經驗所使用委派的次數並沒有這麼頻繁,也不影響到程式的整體流程,但這次變成影響到程式的使用流程。

    是否有其它方式可以改善呢?

    謝謝。

     

     

     



    2011年3月28日 上午 03:51