###### tags: `Clen Code` `Refactor` `CSharp` # Use CancellationTokenSource design the rs232 wait read mechanism 近期須做一些RS232讀取設備相關專案,寫了一個下完指令等待讀取的方法,特別寫一篇[Use Delgate to Clean Code Record](https://hackmd.io/@spyua/r11q8hY7c)。 跟朋友分享過後,他提出其實用CancellationTokenSource就可以達到設計等待讀取時間這件事情。 ## 原先程式碼 ``` csharp = public async Task<double> GetCrossTLK() { var tokenSouce = new CancellationTokenSource(); CancellationToken ct = tokenSouce.Token; // Send Command Com.DiscardInBuffer(); Com.Write("GTLK\r"); var readBuffer = new List<byte>(); SpinWait.SpinUntil(() => false, 50); try { // Read Task var readTask = Task.Run(() => { ct.ThrowIfCancellationRequested(); var endTagIndex = 0; while (endTagIndex <= 0) { byte[] readBufftemp = new byte[Com.BytesToRead]; Com.Read(readBufftemp, 0, Com.BytesToRead); readBuffer.AddRange(readBufftemp); Array.Clear(readBufftemp, 0, readBufftemp.Length); endTagIndex = Array.IndexOf(readBuffer.ToArray(), RS232EndTag); if (ct.IsCancellationRequested) { ct.ThrowIfCancellationRequested(); } } }, tokenSouce.Token); // Timeout if (await Task.WhenAny(readTask, Task.Delay(5000)) == readTask) { // Parase var value = readBuffer.ToArray(); var trkValue = Encoding.UTF8.GetString(value); var tlkValue = Convert.ToDouble(trkValue) * -0.01; tokenSouce.Cancel(); return tlkValue; } else { tokenSouce.Cancel(); throw new Exception("Read Time Out!"); } } catch { throw; } } ``` 上述程式碼可看到使用CancellationTokenSource去手動取消Task,而偵測是否Read逾時這件事情則是用另一個Task Delay去實作。 但實際CancellationTokenSource是可以設定Token的有效時間,只要在New Instance時注入TimeSpan或是毫秒Delay時間。 ![](https://i.imgur.com/sFy39F2.png) 當注入限制時間數字後,Token除了可以使用Cancel去取消外,時間一到Token就會設為已過期狀態。 ## Clean Code 在原本的程式碼我們在New Cancel Token時帶入時間設置。 ```csharp = var token = new CancellationTokenSource(TimeSpan.FromMilliseconds(5000)); ``` 接著更改我們開始讀取的While條件,已Token是否逾期為判定,而讀取時如果讀到結尾符則取消讀取迴圈進入解析區段 ```csharp = while (!token.IsCancellationRequested) { var readBufftemp = new byte[Com.BytesToRead]; Com.Read(readBufftemp, 0, Com.BytesToRead); readBuffer.AddRange(readBufftemp); Array.Clear(readBufftemp, 0, readBufftemp.Length); if (Array.IndexOf(readBuffer.ToArray(), RS232EndTag) > 0) break; } // Timeout if (token.IsCancellationRequested) { throw new Exception("Read Time Out!"); } // Parase ``` 這樣撰寫我們就可以完全省掉原本用Task Delay去做逾時機制的程式碼區段,整段處理完後會如下 ```csharp = public async Task<double> GetCrossTLK() { var token = new CancellationTokenSource(TimeSpan.FromMilliseconds(5000)); // Send Command Com.DiscardInBuffer(); Com.Write("GTLK\r"); var readBuffer = new List<byte>(); await Task.Delay(TimeSpan.FromMilliseconds(50)); try { // Read Task var readTask = Task.Run(() => { ct.ThrowIfCancellationRequested(); while (!token.IsCancellationRequested) { var readBufftemp = new byte[Com.BytesToRead]; Com.Read(readBufftemp, 0, Com.BytesToRead); readBuffer.AddRange(readBufftemp); Array.Clear(readBufftemp, 0, readBufftemp.Length); if (Array.IndexOf(readBuffer.ToArray(), RS232EndTag) > 0) break; } // Timeout if (token.IsCancellationRequested) { throw new Exception("Read Time Out!"); } // Parase var value = readBuffer.ToArray(); var trkValue = Encoding.UTF8.GetString(value); var tlkValue = Convert.ToDouble(trkValue) * -0.01; return tlkValue; } catch { throw; } } ```