###### 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時間。

當注入限制時間數字後,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;
}
}
```